Status EOFRunner::getInfo(TypeExplain** explain, PlanInfo** planInfo) const { if (NULL != explain) { *explain = new TypeExplain; // Fill in mandatory fields. (*explain)->setN(0); (*explain)->setNScannedObjects(0); (*explain)->setNScanned(0); // Fill in all the main fields that don't have a default in the explain data structure. (*explain)->setCursor("BasicCursor"); (*explain)->setScanAndOrder(false); (*explain)->setIsMultiKey(false); (*explain)->setIndexOnly(false); (*explain)->setNYields(0); (*explain)->setNChunkSkips(0); TypeExplain* allPlans = new TypeExplain; allPlans->setCursor("BasicCursor"); (*explain)->addToAllPlans(allPlans); // ownership xfer (*explain)->setNScannedObjectsAllPlans(0); (*explain)->setNScannedAllPlans(0); } else if (NULL != planInfo) { *planInfo = new PlanInfo(); (*planInfo)->planSummary = "EOF"; } return Status::OK(); }
Status EOFRunner::getExplainPlan(TypeExplain** explain) const { *explain = new TypeExplain; // Fill in mandatory fields. (*explain)->setN(0); (*explain)->setNScannedObjects(0); (*explain)->setNScanned(0); // Fill in all the main fields that don't have a default in the explain data structure. (*explain)->setCursor("BasicCursor"); (*explain)->setScanAndOrder(false); (*explain)->setIsMultiKey(false); (*explain)->setIndexOnly(false); (*explain)->setNYields(0); (*explain)->setNChunkSkips(0); TypeExplain* allPlans = new TypeExplain; allPlans->setCursor("BasicCursor"); (*explain)->addToAllPlans(allPlans); // ownership xfer (*explain)->setNScannedObjectsAllPlans(0); (*explain)->setNScannedAllPlans(0); return Status::OK(); }
Status explainIntersectPlan(const PlanStageStats& stats, TypeExplain** explainOut, bool fullDetails) { auto_ptr<TypeExplain> res(new TypeExplain); res->setCursor("Complex Plan"); res->setN(stats.common.advanced); // Sum the various counters at the leaves. vector<const PlanStageStats*> leaves; getLeafNodes(stats, &leaves); long long nScanned = 0; long long nScannedObjects = 0; for (size_t i = 0; i < leaves.size(); ++i) { TypeExplain* leafExplain; explainPlan(*leaves[i], &leafExplain, false); nScanned += leafExplain->getNScanned(); nScannedObjects += leafExplain->getNScannedObjects(); delete leafExplain; } res->setNScanned(nScanned); // XXX: this isn't exactly "correct" -- for ixscans we have to find out if it's part of a // subtree rooted at a fetch, etc. etc. do we want to just add the # of advances of a // fetch node minus the number of alreadyHasObj for those nodes? res->setNScannedObjects(nScannedObjects); uint64_t chunkSkips = 0; const PlanStageStats* shardFilter = findNode(&stats, STAGE_SHARDING_FILTER); if (NULL != shardFilter) { const ShardingFilterStats* sfs = static_cast<const ShardingFilterStats*>(shardFilter->specific.get()); chunkSkips = sfs->chunkSkips; } res->setNChunkSkips(chunkSkips); if (fullDetails) { res->setNYields(stats.common.yields); BSONObjBuilder bob; statsToBSON(stats, &bob); res->stats = bob.obj(); } *explainOut = res.release(); return Status::OK(); }
Status explainMultiPlan(const PlanStageStats& stats, const std::vector<PlanStageStats*>& candidateStats, QuerySolution* solution, TypeExplain** explain) { invariant(explain); Status status = explainPlan(stats, explain, true /* full details */); if (!status.isOK()) { return status; } // TODO Hook the cached plan if there was one. // (*explain)->setOldPlan(???); // // Alternative plans' explains // // We get information about all the plans considered and hook them up the the main // explain structure. If we fail to get any of them, we still return the main explain. // Make sure we initialize the "*AllPlans" fields with the plan that was chose. // TypeExplain* chosenPlan = NULL; status = explainPlan(stats, &chosenPlan, false /* no full details */); if (!status.isOK()) { return status; } (*explain)->addToAllPlans(chosenPlan); // ownership xfer size_t nScannedObjectsAllPlans = chosenPlan->getNScannedObjects(); size_t nScannedAllPlans = chosenPlan->getNScanned(); for (std::vector<PlanStageStats*>::const_iterator it = candidateStats.begin(); it != candidateStats.end(); ++it) { TypeExplain* candidateExplain = NULL; status = explainPlan(**it, &candidateExplain, false /* no full details */); if (status != Status::OK()) { continue; } (*explain)->addToAllPlans(candidateExplain); // ownership xfer nScannedObjectsAllPlans += candidateExplain->getNScannedObjects(); nScannedAllPlans += candidateExplain->getNScanned(); } (*explain)->setNScannedObjectsAllPlans(nScannedObjectsAllPlans); (*explain)->setNScannedAllPlans(nScannedAllPlans); if (NULL != solution) { (*explain)->setIndexFilterApplied(solution->indexFilterApplied); } return Status::OK(); }
// TODO: This is temporary and should get deleted. There are a few small ways in which // this differs from 2.6 explain, but I'm not too worried because this entire format is // going away soon: // 1) 'indexBounds' field excluded from idhack explain. // 2) 'filterSet' field (for index filters) excluded. Status Explain::legacyExplain(PlanExecutor* exec, TypeExplain** explain) { invariant(exec); invariant(explain); scoped_ptr<PlanStageStats> stats(exec->getStats()); if (NULL == stats.get()) { return Status(ErrorCodes::InternalError, "no stats available to explain plan"); } // Special explain format for EOF. if (STAGE_EOF == stats->stageType) { *explain = new TypeExplain(); // Fill in mandatory fields. (*explain)->setN(0); (*explain)->setNScannedObjects(0); (*explain)->setNScanned(0); // Fill in all the main fields that don't have a default in the explain data structure. (*explain)->setCursor("BasicCursor"); (*explain)->setScanAndOrder(false); (*explain)->setIsMultiKey(false); (*explain)->setIndexOnly(false); (*explain)->setNYields(0); (*explain)->setNChunkSkips(0); TypeExplain* allPlans = new TypeExplain; allPlans->setCursor("BasicCursor"); (*explain)->addToAllPlans(allPlans); // ownership xfer (*explain)->setNScannedObjectsAllPlans(0); (*explain)->setNScannedAllPlans(0); return Status::OK(); } // Special explain format for idhack. vector<PlanStageStats*> statNodes; flattenStatsTree(stats.get(), &statNodes); PlanStageStats* idhack = NULL; for (size_t i = 0; i < statNodes.size(); i++) { if (STAGE_IDHACK == statNodes[i]->stageType) { idhack = statNodes[i]; break; } } if (NULL != idhack) { // Explain format does not match 2.4 and is intended // to indicate clearly that the ID hack has been applied. *explain = new TypeExplain(); IDHackStats* idhackStats = static_cast<IDHackStats*>(idhack->specific.get()); (*explain)->setCursor("IDCursor"); (*explain)->setIDHack(true); (*explain)->setN(stats->common.advanced); (*explain)->setNScanned(idhackStats->keysExamined); (*explain)->setNScannedObjects(idhackStats->docsExamined); return Status::OK(); } Status status = explainPlan(*stats, explain, true /* full details */); if (!status.isOK()) { return status; } // Fill in explain fields that are accounted by on the runner level. TypeExplain* chosenPlan = NULL; explainPlan(*stats, &chosenPlan, false /* no full details */); if (chosenPlan) { (*explain)->addToAllPlans(chosenPlan); } (*explain)->setNScannedObjectsAllPlans((*explain)->getNScannedObjects()); (*explain)->setNScannedAllPlans((*explain)->getNScanned()); return Status::OK(); }
Status explainPlan(const PlanStageStats& stats, TypeExplain** explainOut, bool fullDetails) { // // Temporary explain for index intersection // if (isIntersectPlan(stats)) { return explainIntersectPlan(stats, explainOut, fullDetails); } // // Legacy explain implementation // // Descend the plan looking for structural properties: // + Are there any OR clauses? If so, explain each branch. // + What type(s) are the leaf nodes and what are their properties? // + Did we need a sort? bool covered = true; bool sortPresent = false; size_t chunkSkips = 0; const PlanStageStats* orStage = NULL; const PlanStageStats* root = &stats; const PlanStageStats* leaf = root; while (leaf->children.size() > 0) { // We shouldn't be here if there are any ANDs if (leaf->children.size() > 1) { verify(isOrStage(leaf->stageType)); } if (isOrStage(leaf->stageType)) { orStage = leaf; break; } if (leaf->stageType == STAGE_FETCH) { covered = false; } if (leaf->stageType == STAGE_SORT) { sortPresent = true; } if (STAGE_SHARDING_FILTER == leaf->stageType) { const ShardingFilterStats* sfs = static_cast<const ShardingFilterStats*>(leaf->specific.get()); chunkSkips = sfs->chunkSkips; } leaf = leaf->children[0]; } auto_ptr<TypeExplain> res(new TypeExplain); // Accounting for 'nscanned' and 'nscannedObjects' is specific to the kind of leaf: // // + on collection scan, both are the same; all the documents retrieved were // fetched in practice. To get how many documents were retrieved, one simply // looks at the number of 'advanced' in the stats. // // + on an index scan, we'd neeed to look into the index scan cursor to extract the // number of keys that cursor retrieved, and into the stage's stats 'advanced' for // nscannedObjects', which would be the number of keys that survived the IXSCAN // filter. Those keys would have been FETCH-ed, if a fetch is present. if (orStage != NULL) { size_t nScanned = 0; size_t nScannedObjects = 0; const std::vector<PlanStageStats*>& children = orStage->children; for (std::vector<PlanStageStats*>::const_iterator it = children.begin(); it != children.end(); ++it) { TypeExplain* childExplain = NULL; explainPlan(**it, &childExplain, false /* no full details */); if (childExplain) { // 'res' takes ownership of 'childExplain'. res->addToClauses(childExplain); nScanned += childExplain->getNScanned(); // We don't necessarilly fetch on a branch, but the old query framework // did. We're still emulating the number it would have produced. nScannedObjects += childExplain->getNScanned(); } } res->setNScanned(nScanned); res->setNScannedObjects(nScannedObjects); } else if (leaf->stageType == STAGE_COLLSCAN) { CollectionScanStats* csStats = static_cast<CollectionScanStats*>(leaf->specific.get()); res->setCursor("BasicCursor"); res->setNScanned(csStats->docsTested); res->setNScannedObjects(csStats->docsTested); res->setIndexOnly(false); res->setIsMultiKey(false); } else if (leaf->stageType == STAGE_GEO_2D) { // Cursor name depends on type of GeoBrowse. // TODO: We could omit the shape from the cursor name. TwoDStats* nStats = static_cast<TwoDStats*>(leaf->specific.get()); res->setCursor("GeoBrowse-" + nStats->type); res->setNScanned(leaf->common.works); res->setNScannedObjects(leaf->common.works); // XXX: adding empty index bounds for backwards compatibility. res->setIndexBounds(BSONObj()); // TODO: Could be multikey. res->setIsMultiKey(false); res->setIndexOnly(false); } else if (leaf->stageType == STAGE_GEO_NEAR_2DSPHERE) { // TODO: This is kind of a lie for STAGE_GEO_NEAR_2DSPHERE. res->setCursor("S2NearCursor"); // The first work() is an init. Every subsequent work examines a document. res->setNScanned(leaf->common.works); res->setNScannedObjects(leaf->common.works); // XXX: adding empty index bounds for backwards compatibility. res->setIndexBounds(BSONObj()); // TODO: Could be multikey. res->setIsMultiKey(false); res->setIndexOnly(false); } else if (leaf->stageType == STAGE_GEO_NEAR_2D) { TwoDNearStats* nStats = static_cast<TwoDNearStats*>(leaf->specific.get()); res->setCursor("GeoSearchCursor"); // The first work() is an init. Every subsequent work examines a document. res->setNScanned(nStats->nscanned); res->setNScannedObjects(nStats->objectsLoaded); // XXX: adding empty index bounds for backwards compatibility. res->setIndexBounds(BSONObj()); // TODO: Could be multikey. res->setIsMultiKey(false); res->setIndexOnly(false); } else if (leaf->stageType == STAGE_TEXT) { TextStats* tStats = static_cast<TextStats*>(leaf->specific.get()); res->setCursor("TextCursor"); res->setNScanned(tStats->keysExamined); res->setNScannedObjects(tStats->fetches); } else if (leaf->stageType == STAGE_IXSCAN) { IndexScanStats* indexStats = static_cast<IndexScanStats*>(leaf->specific.get()); verify(indexStats); string direction = indexStats->direction > 0 ? "" : " reverse"; res->setCursor(indexStats->indexType + " " + indexStats->indexName + direction); res->setNScanned(indexStats->keysExamined); // If we're covered, that is, no FETCH is present, then, by definition, // nScannedObject would be zero because no full document would have been fetched // from disk. res->setNScannedObjects(covered ? 0 : leaf->common.advanced); res->setIndexBounds(indexStats->indexBounds); res->setIsMultiKey(indexStats->isMultiKey); res->setIndexOnly(covered); } else if (leaf->stageType == STAGE_DISTINCT) { DistinctScanStats* dss = static_cast<DistinctScanStats*>(leaf->specific.get()); verify(dss); res->setCursor("DistinctCursor"); res->setN(dss->keysExamined); res->setNScanned(dss->keysExamined); // Distinct hack stage is fully covered. res->setNScannedObjects(0); } else { return Status(ErrorCodes::InternalError, "cannot interpret execution plan"); } // How many documents did the query return? res->setN(root->common.advanced); res->setScanAndOrder(sortPresent); res->setNChunkSkips(chunkSkips); // Statistics for the plan (appear only in a detailed mode) // TODO: if we can get this from the runner, we can kill "detailed mode" if (fullDetails) { res->setNYields(root->common.yields); BSONObjBuilder bob; statsToBSON(*root, &bob); res->stats = bob.obj(); } *explainOut = res.release(); return Status::OK(); }
Status explainPlan(const PlanStageStats& stats, TypeExplain** explain, bool fullDetails) { auto_ptr<TypeExplain> res(new TypeExplain); // Descend the plan looking for structural properties: // + is there any 'or's (TODO ands)? if so, prepare to explain each branch recursively // + is is a collection scan or a an index scan? // + if the latter, was it covered? // + was a sort necessary? // // TODO: For now, we assume that at most one index is used in a plan bool covered = true; bool sortPresent = false; const PlanStageStats* logicalStage = NULL; const PlanStageStats* root = &stats; const PlanStageStats* leaf = root; while (leaf->children.size() > 0) { // We're failing a plan with multiple children other than OR. // TODO: explain richer plans. if (leaf->children.size() > 1 && !isLogicalStage(leaf->stageType)) { res->setCursor("Complex Plan"); res->setNScanned(0); res->setNScannedObjects(0); *explain = res.release(); return Status::OK(); } if (isLogicalStage(leaf->stageType)) { logicalStage = leaf; break; } if (leaf->stageType == STAGE_FETCH) { covered = false; } if (leaf->stageType == STAGE_SORT) { sortPresent = true; } leaf = leaf->children[0]; } // How many documents did the query return? res->setN(root->common.advanced); // Accounting for 'nscanned' and 'nscannedObjects' is specific to the kind of leaf: // // + on collection scan, both are the same; all the documents retrieved were // fetched in practice. To get how many documents were retrieved, one simply // looks at the number of 'advanced' in the stats. // // + on an index scan, we'd neeed to look into the index scan cursor to extract the // number of keys that cursor retrieved, and into the stage's stats 'advanced' for // nscannedObjects', which would be the number of keys that survived the IXSCAN // filter. Those keys would have been FETCH-ed, if a fetch is present. if (logicalStage != NULL) { uint64_t nScanned = 0; uint64_t nScannedObjects = 0; bool isMultiKey = false; bool isIndexOnly = covered; const std::vector<PlanStageStats*>& children = logicalStage->children; for (std::vector<PlanStageStats*>::const_iterator it = children.begin(); it != children.end(); ++it) { TypeExplain* childExplain = NULL; explainPlan(**it, &childExplain, false /* no full details */); if (childExplain) { res->addToClauses(childExplain); nScanned += childExplain->getNScanned(); // We don't necessarilly fetch on a branch, but the old query framework // did. We're still emulating the number it would have produced. nScannedObjects += childExplain->getNScanned(); isMultiKey |= childExplain->getIsMultiKey(); isIndexOnly &= childExplain->getIndexOnly(); } } res->setNScanned(nScanned); res->setNScannedObjects(nScannedObjects); } else if (leaf->stageType == STAGE_COLLSCAN) { CollectionScanStats* csStats = static_cast<CollectionScanStats*>(leaf->specific.get()); res->setCursor("BasicCursor"); res->setNScanned(csStats->docsTested); res->setNScannedObjects(csStats->docsTested); } else if (leaf->stageType == STAGE_GEO_NEAR_2DSPHERE) { // TODO: This is kind of a lie for STAGE_GEO_NEAR_2DSPHERE. res->setCursor("S2NearCursor"); // The first work() is an init. Every subsequent work examines a document. res->setNScanned(leaf->common.works); res->setNScannedObjects(leaf->common.works); // TODO: Could be multikey. res->setIsMultiKey(false); res->setIndexOnly(false); } else if (leaf->stageType == STAGE_GEO_NEAR_2D) { // TODO: This is kind of a lie. res->setCursor("GeoSearchCursor"); // The first work() is an init. Every subsequent work examines a document. res->setNScanned(leaf->common.works); res->setNScannedObjects(leaf->common.works); // TODO: Could be multikey. res->setIsMultiKey(false); res->setIndexOnly(false); } else if (leaf->stageType == STAGE_IXSCAN) { IndexScanStats* indexStats = static_cast<IndexScanStats*>(leaf->specific.get()); dassert(indexStats); string direction = indexStats > 0 ? "" : " reverse"; res->setCursor(indexStats->indexType + " " + indexStats->indexName + direction); res->setNScanned(indexStats->keysExamined); // If we're covered, that is, no FETCH is present, then, by definition, // nScannedObject would be zero because no full document would have been fetched // from disk. res->setNScannedObjects(covered ? 0 : leaf->common.advanced); res->setIndexBounds(indexStats->indexBounds); res->setIsMultiKey(indexStats->isMultiKey); res->setIndexOnly(covered); } else { return Status(ErrorCodes::InternalError, "cannot interpret execution plan"); } res->setScanAndOrder(sortPresent); // Statistics for the plan (appear only in a detailed mode) // TODO: if we can get this from the runner, we can kill "detailed mode" if (fullDetails) { res->setNYields(root->common.yields); } *explain = res.release(); return Status::OK(); }