diff options
author | Anton Korshunov <anton.korshunov@mongodb.com> | 2021-01-18 22:06:40 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-01-30 03:46:12 +0000 |
commit | c088ca3d8fef6d806f92ea2d5869d8910e5cd30c (patch) | |
tree | e5d69b3273488d9d0c9aa2894184cc8a7545aac9 /src/mongo/db | |
parent | c08203fe14a89e789b7dc5353c427e34c4c2b31e (diff) | |
download | mongo-c088ca3d8fef6d806f92ea2d5869d8910e5cd30c.tar.gz |
SERVER-53270 Include SBE PlanStage tree string into explain output
Diffstat (limited to 'src/mongo/db')
26 files changed, 158 insertions, 54 deletions
diff --git a/src/mongo/db/exec/plan_cache_util.h b/src/mongo/db/exec/plan_cache_util.h index b2a18634016..717f3c879ef 100644 --- a/src/mongo/db/exec/plan_cache_util.h +++ b/src/mongo/db/exec/plan_cache_util.h @@ -114,8 +114,10 @@ void updatePlanCache( if constexpr (std::is_same_v<PlanStageType, std::unique_ptr<sbe::PlanStage>>) { return std::make_pair( plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()), plan_explainer_factory::make(candidates[runnerUpIdx].root.get(), + &candidates[runnerUpIdx].data, candidates[runnerUpIdx].solution.get())); } else { static_assert(std::is_same_v<PlanStageType, PlanStage*>); @@ -139,6 +141,7 @@ void updatePlanCache( auto winnerExplainer = [&]() { if constexpr (std::is_same_v<PlanStageType, std::unique_ptr<sbe::PlanStage>>) { return plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()); } else { static_assert(std::is_same_v<PlanStageType, PlanStage*>); diff --git a/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp b/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp index 914352adce0..8fcf32cb165 100644 --- a/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp +++ b/src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp @@ -231,9 +231,9 @@ TEST_F(SBEParserTest, TestIdenticalDebugOutputAfterParse) { sbe::Parser parser; for (const auto& stage : stages) { - const auto stageText = printer.print(stage.get()); + const auto stageText = printer.print(*stage); const auto parsedStage = parser.parse(nullptr, "testDb", stageText); - const auto stageTextAfterParse = printer.print(parsedStage.get()); + const auto stageTextAfterParse = printer.print(*parsedStage); ASSERT_EQ(stageText, stageTextAfterParse); } } @@ -243,7 +243,7 @@ TEST_F(SBEParserTest, TestPlanNodeIdIsParsed) { sbe::Parser parser; for (const auto& stage : stages) { - const auto stageText = printer.print(stage.get()); + const auto stageText = printer.print(*stage); const auto parsedStage = parser.parse(nullptr, "testDb", stageText); ASSERT_EQ(parsedStage->getCommonStats()->nodeId, planNodeId); } diff --git a/src/mongo/db/exec/sbe/stages/hash_agg.cpp b/src/mongo/db/exec/sbe/stages/hash_agg.cpp index 18e7ee6559a..4c4a5b4f132 100644 --- a/src/mongo/db/exec/sbe/stages/hash_agg.cpp +++ b/src/mongo/db/exec/sbe/stages/hash_agg.cpp @@ -197,16 +197,16 @@ std::vector<DebugPrinter::Block> HashAggStage::debugPrint() const { ret.emplace_back(DebugPrinter::Block("[`")); bool first = true; - for (auto& p : _aggs) { + value::orderedSlotMapTraverse(_aggs, [&](auto slot, auto&& expr) { if (!first) { ret.emplace_back(DebugPrinter::Block("`,")); } - DebugPrinter::addIdentifier(ret, p.first); + DebugPrinter::addIdentifier(ret, slot); ret.emplace_back("="); - DebugPrinter::addBlocks(ret, p.second->debugPrint()); + DebugPrinter::addBlocks(ret, expr->debugPrint()); first = false; - } + }); ret.emplace_back("`]"); DebugPrinter::addNewLine(ret); diff --git a/src/mongo/db/exec/sbe/stages/project.cpp b/src/mongo/db/exec/sbe/stages/project.cpp index a5ab48b1369..10cf54e2b2e 100644 --- a/src/mongo/db/exec/sbe/stages/project.cpp +++ b/src/mongo/db/exec/sbe/stages/project.cpp @@ -100,9 +100,9 @@ std::unique_ptr<PlanStageStats> ProjectStage::getStats(bool includeDebugInfo) co if (includeDebugInfo) { DebugPrinter printer; BSONObjBuilder bob; - for (auto&& [slot, expr] : _projects) { + value::orderedSlotMapTraverse(_projects, [&](auto slot, auto&& expr) { bob.append(str::stream() << slot, printer.print(expr->debugPrint())); - } + }); ret->debugInfo = BSON("projections" << bob.obj()); } @@ -116,19 +116,21 @@ const SpecificStats* ProjectStage::getSpecificStats() const { std::vector<DebugPrinter::Block> ProjectStage::debugPrint() const { auto ret = PlanStage::debugPrint(); + ret.emplace_back("[`"); bool first = true; - for (auto& p : _projects) { + value::orderedSlotMapTraverse(_projects, [&](auto slot, auto&& expr) { if (!first) { ret.emplace_back(DebugPrinter::Block("`,")); } - DebugPrinter::addIdentifier(ret, p.first); + DebugPrinter::addIdentifier(ret, slot); ret.emplace_back("="); - DebugPrinter::addBlocks(ret, p.second->debugPrint()); + DebugPrinter::addBlocks(ret, expr->debugPrint()); first = false; - } + }); ret.emplace_back("`]"); + DebugPrinter::addNewLine(ret); DebugPrinter::addBlocks(ret, _children[0]->debugPrint()); return ret; diff --git a/src/mongo/db/exec/sbe/util/debug_print.cpp b/src/mongo/db/exec/sbe/util/debug_print.cpp index b659f9a7ba6..939779be8de 100644 --- a/src/mongo/db/exec/sbe/util/debug_print.cpp +++ b/src/mongo/db/exec/sbe/util/debug_print.cpp @@ -116,8 +116,8 @@ std::string DebugPrinter::print(const std::vector<Block>& blocks) { return ret; } -std::string DebugPrinter::print(PlanStage* s) { - return print(s->debugPrint()); +std::string DebugPrinter::print(const PlanStage& s) { + return print(s.debugPrint()); } } // namespace sbe } // namespace mongo diff --git a/src/mongo/db/exec/sbe/util/debug_print.h b/src/mongo/db/exec/sbe/util/debug_print.h index 7b58391e207..19e29d7bdf4 100644 --- a/src/mongo/db/exec/sbe/util/debug_print.h +++ b/src/mongo/db/exec/sbe/util/debug_print.h @@ -116,7 +116,7 @@ public: std::make_move_iterator(blocks.begin()), std::make_move_iterator(blocks.end())); } - std::string print(PlanStage* s); + std::string print(const PlanStage& s); std::string print(const std::vector<Block>& blocks); private: diff --git a/src/mongo/db/exec/sbe/values/slot.h b/src/mongo/db/exec/sbe/values/slot.h index 95131fb7fdc..04449a49305 100644 --- a/src/mongo/db/exec/sbe/values/slot.h +++ b/src/mongo/db/exec/sbe/values/slot.h @@ -591,4 +591,20 @@ using SlotVector = std::vector<SlotId>; using SlotIdGenerator = IdGenerator<value::SlotId>; using FrameIdGenerator = IdGenerator<FrameId>; using SpoolIdGenerator = IdGenerator<SpoolId>; + +/** + * Given an unordered slot 'map', calls 'callback' for each slot/value pair in order of ascending + * slot id. + */ +template <typename T, typename C> +void orderedSlotMapTraverse(const SlotMap<T>& map, C callback) { + std::set<SlotId> slots; + for (auto&& elem : map) { + slots.insert(elem.first); + } + + for (auto slot : slots) { + callback(slot, map.at(slot)); + } +} } // namespace mongo::sbe::value diff --git a/src/mongo/db/pipeline/document_source_cursor.h b/src/mongo/db/pipeline/document_source_cursor.h index 6df87f7c706..2d3a5332b10 100644 --- a/src/mongo/db/pipeline/document_source_cursor.h +++ b/src/mongo/db/pipeline/document_source_cursor.h @@ -115,6 +115,10 @@ public: return &_stats; } + const PlanExplainer::ExplainVersion& getExplainVersion() const { + return _exec->getPlanExplainer().getVersion(); + } + protected: DocumentSourceCursor(const CollectionPtr& collection, std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec, diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp index 49e05ead917..4b3bcfac0a6 100644 --- a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp +++ b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp @@ -50,6 +50,16 @@ void collectPlanSummaryStats(const DocSourceType& source, PlanSummaryStats* stat statsOut->accumulate(docSpecificStats.planSummaryStats); } +const PlanExplainer::ExplainVersion& PlanExplainerPipeline::getVersion() const { + static const ExplainVersion kExplainVersion = "1"; + + if (auto docSourceCursor = + dynamic_cast<DocumentSourceCursor*>(_pipeline->getSources().front().get())) { + return docSourceCursor->getExplainVersion(); + } + return kExplainVersion; +} + std::string PlanExplainerPipeline::getPlanSummary() const { if (auto docSourceCursor = dynamic_cast<DocumentSourceCursor*>(_pipeline->getSources().front().get())) { diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.h b/src/mongo/db/pipeline/plan_explainer_pipeline.h index e2021b1c20e..f28adc3c7ea 100644 --- a/src/mongo/db/pipeline/plan_explainer_pipeline.h +++ b/src/mongo/db/pipeline/plan_explainer_pipeline.h @@ -44,6 +44,7 @@ public: return false; } + const ExplainVersion& getVersion() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final; diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index c271cfe1c81..330e2709a6d 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -84,7 +84,6 @@ void generatePlannerInfo(PlanExecutor* exec, BSONObjBuilder* out) { BSONObjBuilder plannerBob(out->subobjStart("queryPlanner")); - plannerBob.append("plannerVersion", QueryPlanner::kPlannerVersion); plannerBob.append("namespace", exec->nss().ns()); // Find whether there is an index filter set for the query shape. The 'indexFilterSet' field @@ -266,6 +265,14 @@ void executePlan(PlanExecutor* exec) { // Discard the resulting documents. } } + +/** + * Returns a BSON document in the form of {explainVersion: <version>} with the 'version' parameter + * serialized into the <version> element. + */ +BSONObj explainVersionToBson(const PlanExplainer::ExplainVersion& version) { + return BSON("explainVersion" << version); +} } // namespace void Explain::explainStages(PlanExecutor* exec, @@ -280,6 +287,9 @@ void Explain::explainStages(PlanExecutor* exec, // Use the stats trees to produce explain BSON. // + auto&& explainer = exec->getPlanExplainer(); + out->appendElements(explainVersionToBson(explainer.getVersion())); + if (verbosity >= ExplainOptions::Verbosity::kQueryPlanner) { generatePlannerInfo(exec, collection, extraInfo, out); } @@ -309,6 +319,8 @@ void Explain::explainPipeline(PlanExecutor* exec, executePlan(pipelineExec); } + auto&& explainer = pipelineExec->getPlanExplainer(); + out->appendElements(explainVersionToBson(explainer.getVersion())); *out << "stages" << Value(pipelineExec->writeExplainOps(verbosity)); explain_common::generateServerInfo(out); @@ -381,14 +393,15 @@ void Explain::planCacheEntryToBSON(const PlanCacheEntry& entry, BSONObjBuilder* } } - auto explainer = stdx::visit( - visit_helper::Overloaded{[](const plan_ranker::StatsDetails&) { - return plan_explainer_factory::make(nullptr); - }, - [](const plan_ranker::SBEStatsDetails&) { - return plan_explainer_factory::make(nullptr, nullptr); - }}, - debugInfo.decision->stats); + auto explainer = + stdx::visit(visit_helper::Overloaded{[](const plan_ranker::StatsDetails&) { + return plan_explainer_factory::make(nullptr); + }, + [](const plan_ranker::SBEStatsDetails&) { + return plan_explainer_factory::make( + nullptr, nullptr, nullptr); + }}, + debugInfo.decision->stats); auto plannerStats = explainer->getCachedPlanStats(debugInfo, ExplainOptions::Verbosity::kQueryPlanner); auto execStats = diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 8089c0b5b8d..e8e6abfb9d5 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -483,7 +483,8 @@ public: invariant(_roots.size() == 1); invariant(_solutions.size() == 1); invariant(_roots[0].first); - auto explainer = plan_explainer_factory::make(_roots[0].first.get(), _solutions[0].get()); + auto explainer = plan_explainer_factory::make( + _roots[0].first.get(), &_roots[0].second, _solutions[0].get()); return explainer->getPlanSummary(); } diff --git a/src/mongo/db/query/plan_executor_factory.cpp b/src/mongo/db/query/plan_executor_factory.cpp index f7e02c57529..d43cdfd9e32 100644 --- a/src/mongo/db/query/plan_executor_factory.cpp +++ b/src/mongo/db/query/plan_executor_factory.cpp @@ -132,7 +132,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make( 5, "SBE plan", "slots"_attr = data.debugString(), - "stages"_attr = sbe::DebugPrinter{}.print(rootStage.get())); + "stages"_attr = sbe::DebugPrinter{}.print(*rootStage)); rootStage->prepare(data.ctx); @@ -164,7 +164,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make( 5, "SBE plan", "slots"_attr = candidates.winner().data.debugString(), - "stages"_attr = sbe::DebugPrinter{}.print(candidates.winner().root.get())); + "stages"_attr = sbe::DebugPrinter{}.print(*candidates.winner().root)); return {{new PlanExecutorSBE(opCtx, std::move(cq), diff --git a/src/mongo/db/query/plan_executor_sbe.cpp b/src/mongo/db/query/plan_executor_sbe.cpp index 11b856b10b2..7c32db8ad00 100644 --- a/src/mongo/db/query/plan_executor_sbe.cpp +++ b/src/mongo/db/query/plan_executor_sbe.cpp @@ -108,7 +108,7 @@ PlanExecutorSBE::PlanExecutorSBE(OperationContext* opCtx, candidates.plans.erase(candidates.plans.begin() + candidates.winnerIdx); } _planExplainer = plan_explainer_factory::make( - _root.get(), _solution.get(), std::move(candidates.plans), isMultiPlan); + _root.get(), &winner.data, _solution.get(), std::move(candidates.plans), isMultiPlan); } void PlanExecutorSBE::saveState() { diff --git a/src/mongo/db/query/plan_explainer.h b/src/mongo/db/query/plan_explainer.h index 96f18f8d9ed..e6d19e32470 100644 --- a/src/mongo/db/query/plan_explainer.h +++ b/src/mongo/db/query/plan_explainer.h @@ -50,6 +50,11 @@ static constexpr int kMaxExplainStatsBSONSizeMB = 10 * 1024 * 1024; class PlanExplainer { public: /** + * A version of the explain format. "1" is used for the classic engine and "2" for SBE. + */ + using ExplainVersion = std::string; + + /** * This pair holds a serialized BSON document that details the plan selected by the query * planner, and optional summary stats for an execution tree if the verbosity level for the * generated stats is 'executionStats' or higher. The format of these stats are opaque to the @@ -66,6 +71,11 @@ public: virtual ~PlanExplainer() = default; /** + * Returns a version of the explain format supported by this explainer. + */ + virtual const ExplainVersion& getVersion() const = 0; + + /** * Returns 'true' if this PlanExplainer can provide information on the winning plan and rejected * candidate plans, meaning that the QueryPlanner generated multiple candidate plans and the * winning plan was chosen by the multi-planner. diff --git a/src/mongo/db/query/plan_explainer_factory.cpp b/src/mongo/db/query/plan_explainer_factory.cpp index da64fec81f5..74d0c1dfd3d 100644 --- a/src/mongo/db/query/plan_explainer_factory.cpp +++ b/src/mongo/db/query/plan_explainer_factory.cpp @@ -43,15 +43,18 @@ std::unique_ptr<PlanExplainer> make(PlanStage* root, const PlanEnumeratorExplain return std::make_unique<PlanExplainerImpl>(root, explainInfo); } -std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution) { - return make(root, solution, {}, false); +std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, + const QuerySolution* solution) { + return make(root, data, solution, {}, false); } std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan) { return std::make_unique<PlanExplainerSBE>( - root, solution, std::move(rejectedCandidates), isMultiPlan); + root, data, solution, std::move(rejectedCandidates), isMultiPlan); } } // namespace mongo::plan_explainer_factory diff --git a/src/mongo/db/query/plan_explainer_factory.h b/src/mongo/db/query/plan_explainer_factory.h index 55e42d90791..f6736067bda 100644 --- a/src/mongo/db/query/plan_explainer_factory.h +++ b/src/mongo/db/query/plan_explainer_factory.h @@ -40,8 +40,11 @@ namespace mongo::plan_explainer_factory { std::unique_ptr<PlanExplainer> make(PlanStage* root); std::unique_ptr<PlanExplainer> make(PlanStage* root, const PlanEnumeratorExplainInfo& enumeratorInfo); -std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution); std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, + const QuerySolution* solution); +std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan); diff --git a/src/mongo/db/query/plan_explainer_impl.cpp b/src/mongo/db/query/plan_explainer_impl.cpp index 78614450060..a04882ce57c 100644 --- a/src/mongo/db/query/plan_explainer_impl.cpp +++ b/src/mongo/db/query/plan_explainer_impl.cpp @@ -536,6 +536,11 @@ void appendMultikeyPaths(const BSONObj& keyPattern, subMultikeyPaths.doneFast(); } +const PlanExplainer::ExplainVersion& PlanExplainerImpl::getVersion() const { + static const ExplainVersion kExplainVersion = "1"; + return kExplainVersion; +} + bool PlanExplainerImpl::isMultiPlan() const { return getStageByType(_root, StageType::STAGE_MULTI_PLAN) != nullptr; } diff --git a/src/mongo/db/query/plan_explainer_impl.h b/src/mongo/db/query/plan_explainer_impl.h index 82d97e5a83b..859b633e0e1 100644 --- a/src/mongo/db/query/plan_explainer_impl.h +++ b/src/mongo/db/query/plan_explainer_impl.h @@ -48,6 +48,7 @@ public: : PlanExplainer{explainInfo}, _root{root} {} PlanExplainerImpl(PlanStage* root) : _root{root} {} + const ExplainVersion& getVersion() const final; bool isMultiPlan() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp index 677e0ed393a..e993b2cf6c0 100644 --- a/src/mongo/db/query/plan_explainer_sbe.cpp +++ b/src/mongo/db/query/plan_explainer_sbe.cpp @@ -298,22 +298,36 @@ PlanSummaryStats collectExecutionStatsSummary(const sbe::PlanStageStats* stats) return summary; } -PlanExplainer::PlanStatsDetails buildPlanStatsDetails(const QuerySolutionNode* node, - const sbe::PlanStageStats* stats, - ExplainOptions::Verbosity verbosity) { +PlanExplainer::PlanStatsDetails buildPlanStatsDetails( + const QuerySolutionNode* node, + const sbe::PlanStageStats* stats, + const boost::optional<BSONObj>& execPlanDebugInfo, + ExplainOptions::Verbosity verbosity) { BSONObjBuilder bob; if (verbosity >= ExplainOptions::Verbosity::kExecStats) { auto summary = collectExecutionStatsSummary(stats); statsToBSON(stats, &bob, &bob); + // At the 'kQueryPlanner' verbosity level we use the QSN-derived format for the given plan, + // and thus the winning plan and rejected plans at this verbosity should display the + // stringified SBE plan, which is added below. However, at the 'kExecStats' the execution + // stats use the PlanStage-derived format for the SBE tree, so there is no need to repeat + // the stringified SBE plan and we only included what's been generated from the + // PlanStageStats. return {bob.obj(), std::move(summary)}; } statsToBSON(node, &bob, &bob); - return {bob.obj(), boost::none}; + invariant(execPlanDebugInfo); + return {BSON("queryPlan" << bob.obj() << "slotBasedPlan" << *execPlanDebugInfo), boost::none}; } } // namespace +const PlanExplainer::ExplainVersion& PlanExplainerSBE::getVersion() const { + static const ExplainVersion kExplainVersion = "2"; + return kExplainVersion; +} + std::string PlanExplainerSBE::getPlanSummary() const { if (!_solution) { return {}; @@ -462,7 +476,7 @@ PlanExplainer::PlanStatsDetails PlanExplainerSBE::getWinningPlanStats( invariant(_root); invariant(_solution); auto stats = _root->getStats(true /* includeDebugInfo */); - return buildPlanStatsDetails(_solution->root(), stats.get(), verbosity); + return buildPlanStatsDetails(_solution->root(), stats.get(), _execPlanDebugInfo, verbosity); } std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansStats( @@ -478,7 +492,9 @@ std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansS invariant(candidate.solution); auto stats = candidate.root->getStats(true /* includeDebugInfo */); - res.push_back(buildPlanStatsDetails(candidate.solution->root(), stats.get(), verbosity)); + auto execPlanDebugInfo = buildExecPlanDebugInfo(candidate.root.get(), &candidate.data); + res.push_back(buildPlanStatsDetails( + candidate.solution->root(), stats.get(), execPlanDebugInfo, verbosity)); } return res; } @@ -491,7 +507,7 @@ std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getCachedPlanStat auto&& stats = decision.getStats<mongo::sbe::PlanStageStats>(); if (verbosity >= ExplainOptions::Verbosity::kExecStats) { for (auto&& planStats : stats.candidatePlanStats) { - res.push_back(buildPlanStatsDetails(nullptr, planStats.get(), verbosity)); + res.push_back(buildPlanStatsDetails(nullptr, planStats.get(), boost::none, verbosity)); } } else { // At the "queryPlanner" verbosity we only need to provide details about the winning plan diff --git a/src/mongo/db/query/plan_explainer_sbe.h b/src/mongo/db/query/plan_explainer_sbe.h index d8056ade4ed..dba5316e82b 100644 --- a/src/mongo/db/query/plan_explainer_sbe.h +++ b/src/mongo/db/query/plan_explainer_sbe.h @@ -41,6 +41,7 @@ namespace mongo { class PlanExplainerSBE final : public PlanExplainer { public: PlanExplainerSBE(const sbe::PlanStage* root, + const stage_builder::PlanStageData* data, const QuerySolution* solution, std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates, bool isMultiPlan) @@ -48,12 +49,14 @@ public: _root{root}, _solution{solution}, _rejectedCandidates{std::move(rejectedCandidates)}, - _isMultiPlan{isMultiPlan} {} + _isMultiPlan{isMultiPlan}, + _execPlanDebugInfo{buildExecPlanDebugInfo(_root, data)} {} bool isMultiPlan() const final { return _isMultiPlan; } + const ExplainVersion& getVersion() const final; std::string getPlanSummary() const final; void getSummaryStats(PlanSummaryStats* statsOut) const final; PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final; @@ -63,9 +66,25 @@ public: ExplainOptions::Verbosity) const final; private: + boost::optional<BSONObj> buildExecPlanDebugInfo( + const sbe::PlanStage* root, const stage_builder::PlanStageData* data) const { + if (root && data) { + return BSON("slots" << data->debugString() << "stages" + << sbe::DebugPrinter().print(*_root)); + } + return boost::none; + } + const sbe::PlanStage* _root{nullptr}; const QuerySolution* _solution{nullptr}; const std::vector<sbe::plan_ranker::CandidatePlan> _rejectedCandidates; const bool _isMultiPlan{false}; + // Contains information about the slots returned by the PlanStage tree, along with the tree + // itself, serialized into a string. This optional object is then included into explain output + // and is only initialized when the _root of the PlanStage tree is available. The only case when + // it's not available is when PlanStatsDetails are generated from the plan cache (by + // calling getCachedPlanStats()), in which case this debug info is already included into the + // plan cache entry as part of a serialized winning plan. + boost::optional<BSONObj> _execPlanDebugInfo; }; } // namespace mongo diff --git a/src/mongo/db/query/plan_ranker_util.h b/src/mongo/db/query/plan_ranker_util.h index 06557f2b574..58bcc16d3df 100644 --- a/src/mongo/db/query/plan_ranker_util.h +++ b/src/mongo/db/query/plan_ranker_util.h @@ -84,8 +84,8 @@ StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan( candidates[i].solution->_enumeratorExplainInfo); } else { static_assert(std::is_same_v<PlanStageStatsType, mongo::sbe::PlanStageStats>); - return plan_explainer_factory::make(candidates[i].root.get(), - candidates[i].solution.get()); + return plan_explainer_factory::make( + candidates[i].root.get(), &candidates[i].data, candidates[i].solution.get()); } }(); @@ -143,8 +143,8 @@ StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan( // For SBE, we need to store a serialized winning plan within the ranking decision to be // able to included it into the explain output for a cached plan stats, since we cannot // reconstruct it from a PlanStageStats tree. - auto explainer = - plan_explainer_factory::make(candidates[0].root.get(), candidates[0].solution.get()); + auto explainer = plan_explainer_factory::make( + candidates[0].root.get(), &candidates[0].data, candidates[0].solution.get()); auto&& [stats, _] = explainer->getWinningPlanStats(ExplainOptions::Verbosity::kQueryPlanner); SBEStatsDetails details; diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index a910d7504d3..70f62ecc418 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -344,9 +344,6 @@ bool providesSort(const CanonicalQuery& query, const BSONObj& kp) { return query.getQueryRequest().getSort().isPrefixOf(kp, SimpleBSONElementComparator::kInstance); } -// static -const int QueryPlanner::kPlannerVersion = 1; - StatusWith<std::unique_ptr<PlanCacheIndexTree>> QueryPlanner::cacheDataFromTaggedTree( const MatchExpression* const taggedTree, const vector<IndexEntry>& relevantIndices) { if (!taggedTree) { diff --git a/src/mongo/db/query/query_planner.h b/src/mongo/db/query/query_planner.h index 236ec4f0269..dcd6ca6e542 100644 --- a/src/mongo/db/query/query_planner.h +++ b/src/mongo/db/query/query_planner.h @@ -80,9 +80,6 @@ public: std::map<IndexEntry::Identifier, size_t> indexMap; }; - // Identifies the version of the query planner module. Reported in explain. - static const int kPlannerVersion; - /** * Returns the list of possible query solutions for the provided 'query'. Uses the indices and * other data in 'params' to determine the set of available plans. diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp index aece175a58f..321410b9a1a 100644 --- a/src/mongo/db/query/sbe_cached_solution_planner.cpp +++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp @@ -52,7 +52,8 @@ CandidatePlans CachedSolutionPlanner::plan( invariant(candidates.size() == 1); return std::move(candidates[0]); }(); - auto explainer = plan_explainer_factory::make(candidate.root.get(), candidate.solution.get()); + auto explainer = plan_explainer_factory::make( + candidate.root.get(), &candidate.data, candidate.solution.get()); if (candidate.failed) { // On failure, fall back to replanning the whole query. We neither evict the existing cache @@ -123,7 +124,7 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache) const { _opCtx, _collection, _cq, *solutions[0], _yieldPolicy); prepareExecutionPlan(root.get(), &data); - auto explainer = plan_explainer_factory::make(root.get(), solutions[0].get()); + auto explainer = plan_explainer_factory::make(root.get(), &data, solutions[0].get()); LOGV2_DEBUG( 2058101, 1, @@ -153,6 +154,7 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache) const { MultiPlanner multiPlanner{_opCtx, _collection, _cq, cachingMode, _yieldPolicy}; auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots)); auto explainer = plan_explainer_factory::make(candidates[winnerIdx].root.get(), + &candidates[winnerIdx].data, candidates[winnerIdx].solution.get()); LOGV2_DEBUG(2058201, 1, diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp index b44d786ed0d..b51c642a447 100644 --- a/src/mongo/db/query/sbe_multi_planner.cpp +++ b/src/mongo/db/query/sbe_multi_planner.cpp @@ -65,7 +65,8 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans( LOGV2_DEBUG( 4822875, 5, "Winning solution", "bestSolution"_attr = redact(winner.solution->toString())); - auto explainer = plan_explainer_factory::make(winner.root.get(), winner.solution.get()); + auto explainer = + plan_explainer_factory::make(winner.root.get(), &winner.data, winner.solution.get()); LOGV2_DEBUG(4822876, 2, "Winning plan", "planSummary"_attr = explainer->getPlanSummary()); // Close all candidate plans but the winner. |