summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorAnton Korshunov <anton.korshunov@mongodb.com>2021-01-18 22:06:40 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-01-30 03:46:12 +0000
commitc088ca3d8fef6d806f92ea2d5869d8910e5cd30c (patch)
treee5d69b3273488d9d0c9aa2894184cc8a7545aac9 /src/mongo/db
parentc08203fe14a89e789b7dc5353c427e34c4c2b31e (diff)
downloadmongo-c088ca3d8fef6d806f92ea2d5869d8910e5cd30c.tar.gz
SERVER-53270 Include SBE PlanStage tree string into explain output
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/exec/plan_cache_util.h3
-rw-r--r--src/mongo/db/exec/sbe/parser/sbe_parser_test.cpp6
-rw-r--r--src/mongo/db/exec/sbe/stages/hash_agg.cpp8
-rw-r--r--src/mongo/db/exec/sbe/stages/project.cpp14
-rw-r--r--src/mongo/db/exec/sbe/util/debug_print.cpp4
-rw-r--r--src/mongo/db/exec/sbe/util/debug_print.h2
-rw-r--r--src/mongo/db/exec/sbe/values/slot.h16
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.h4
-rw-r--r--src/mongo/db/pipeline/plan_explainer_pipeline.cpp10
-rw-r--r--src/mongo/db/pipeline/plan_explainer_pipeline.h1
-rw-r--r--src/mongo/db/query/explain.cpp31
-rw-r--r--src/mongo/db/query/get_executor.cpp3
-rw-r--r--src/mongo/db/query/plan_executor_factory.cpp4
-rw-r--r--src/mongo/db/query/plan_executor_sbe.cpp2
-rw-r--r--src/mongo/db/query/plan_explainer.h10
-rw-r--r--src/mongo/db/query/plan_explainer_factory.cpp9
-rw-r--r--src/mongo/db/query/plan_explainer_factory.h5
-rw-r--r--src/mongo/db/query/plan_explainer_impl.cpp5
-rw-r--r--src/mongo/db/query/plan_explainer_impl.h1
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.cpp30
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.h21
-rw-r--r--src/mongo/db/query/plan_ranker_util.h8
-rw-r--r--src/mongo/db/query/query_planner.cpp3
-rw-r--r--src/mongo/db/query/query_planner.h3
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.cpp6
-rw-r--r--src/mongo/db/query/sbe_multi_planner.cpp3
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.