/** * Copyright (C) 2013 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/s/cluster_explain.h" #include "mongo/bson/bsonmisc.h" namespace mongo { using std::vector; const char* ClusterExplain::kSingleShard = "SINGLE_SHARD"; const char* ClusterExplain::kMergeFromShards = "SHARD_MERGE"; const char* ClusterExplain::kMergeSortFromShards = "SHARD_MERGE_SORT"; const char* ClusterExplain::kWriteOnShards = "SHARD_WRITE"; namespace { // // BSON size limit management: these functions conditionally append to a // BSON object or BSON array buidlder, depending on whether or not the // maximum user size for a BSON object will be exceeded. // bool appendIfRoom(BSONObjBuilder* bob, const BSONObj& toAppend, StringData fieldName) { if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) { bob->append(fieldName, toAppend); return true; } // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating // that data has been truncated. if (bob->len() < BSONObjMaxUserSize) { bob->append("warning", "output truncated due to nearing BSON max user size"); } return false; } bool appendToArrayIfRoom(BSONArrayBuilder* arrayBuilder, const BSONElement& toAppend) { if ((arrayBuilder->len() + toAppend.size()) < BSONObjMaxUserSize) { arrayBuilder->append(toAppend); return true; } // Unless 'arrayBuilder' has already exceeded the max BSON user size, add a warning // indicating that data has been truncated. if (arrayBuilder->len() < BSONObjMaxUserSize) { arrayBuilder->append(BSON("warning" << "output truncated due to nearing BSON max user size")); } return false; } bool appendElementsIfRoom(BSONObjBuilder* bob, const BSONObj& toAppend) { if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) { bob->appendElements(toAppend); return true; } // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating // that data has been truncated. if (bob->len() < BSONObjMaxUserSize) { bob->append("warning", "output truncated due to nearing BSON max user size"); } return false; } } // namespace // static void ClusterExplain::wrapAsExplain(const BSONObj& cmdObj, ExplainCommon::Verbosity verbosity, BSONObjBuilder* out) { out->append("explain", cmdObj); out->append("verbosity", ExplainCommon::verbosityString(verbosity)); // If the command has a readPreference, then pull it up to the top level. if (cmdObj.hasField("$readPreference")) { out->append("$queryOptions", cmdObj["$readPreference"].wrap()); } } // static Status ClusterExplain::validateShardResults( const vector& shardResults) { // Error we didn't get results from any shards. if (shardResults.empty()) { return Status(ErrorCodes::InternalError, "no shards found for explain"); } // Count up the number of shards that have execution stats and all plans // execution level information. size_t numShardsExecStats = 0; size_t numShardsAllPlansStats = 0; // Check that the result from each shard has a true value for "ok" and has // the expected "queryPlanner" field. for (size_t i = 0; i < shardResults.size(); i++) { if (!shardResults[i].result["ok"].trueValue()) { // Try to pass up the error code from the shard. ErrorCodes::Error error = ErrorCodes::OperationFailed; if (shardResults[i].result["code"].isNumber()) { error = ErrorCodes::fromInt(shardResults[i].result["code"].numberInt()); } return Status(error, str::stream() << "Explain command on shard " << shardResults[i].target.toString() << " failed, caused by: " << shardResults[i].result); } if (Object != shardResults[i].result["queryPlanner"].type()) { return Status(ErrorCodes::OperationFailed, str::stream() << "Explain command on shard " << shardResults[i].target.toString() << " failed, caused by: " << shardResults[i].result); } if (shardResults[i].result.hasField("executionStats")) { numShardsExecStats++; BSONObj execStats = shardResults[i].result["executionStats"].Obj(); if (execStats.hasField("allPlansExecution")) { numShardsAllPlansStats++; } } } // Either all shards should have execution stats info, or none should. if (0 != numShardsExecStats && shardResults.size() != numShardsExecStats) { return Status(ErrorCodes::InternalError, str::stream() << "Only " << numShardsExecStats << "/" << shardResults.size() << " had executionStats explain information."); } // Either all shards should have all plans execution stats, or none should. if (0 != numShardsAllPlansStats && shardResults.size() != numShardsAllPlansStats) { return Status(ErrorCodes::InternalError, str::stream() << "Only " << numShardsAllPlansStats << "/" << shardResults.size() << " had allPlansExecution explain information."); } return Status::OK(); } // static const char* ClusterExplain::getStageNameForReadOp( const vector& shardResults, const BSONObj& explainObj) { if (shardResults.size() == 1) { return kSingleShard; } else if (explainObj.hasField("sort")) { return kMergeSortFromShards; } else { return kMergeFromShards; } } // static void ClusterExplain::buildPlannerInfo(const vector& shardResults, const char* mongosStageName, BSONObjBuilder* out) { BSONObjBuilder queryPlannerBob(out->subobjStart("queryPlanner")); queryPlannerBob.appendNumber("mongosPlannerVersion", 1); BSONObjBuilder winningPlanBob(queryPlannerBob.subobjStart("winningPlan")); winningPlanBob.append("stage", mongosStageName); BSONArrayBuilder shardsBuilder(winningPlanBob.subarrayStart("shards")); for (size_t i = 0; i < shardResults.size(); i++) { BSONObjBuilder singleShardBob(shardsBuilder.subobjStart()); BSONObj queryPlanner = shardResults[i].result["queryPlanner"].Obj(); BSONObj serverInfo = shardResults[i].result["serverInfo"].Obj(); singleShardBob.append("shardName", shardResults[i].shardTarget.getName()); std::string connStr = shardResults[i].shardTarget.getAddress().toString(); singleShardBob.append("connectionString", connStr); appendIfRoom(&singleShardBob, serverInfo, "serverInfo"); appendElementsIfRoom(&singleShardBob, queryPlanner); singleShardBob.doneFast(); } shardsBuilder.doneFast(); winningPlanBob.doneFast(); queryPlannerBob.doneFast(); } // static void ClusterExplain::buildExecStats(const vector& shardResults, const char* mongosStageName, long long millisElapsed, BSONObjBuilder* out) { if (!shardResults[0].result.hasField("executionStats")) { // The shards don't have execution stats info. Bail out without adding anything // to 'out'. return; } BSONObjBuilder executionStatsBob(out->subobjStart("executionStats")); // Collect summary stats from the shards. long long nReturned = 0; long long keysExamined = 0; long long docsExamined = 0; long long totalChildMillis = 0; for (size_t i = 0; i < shardResults.size(); i++) { BSONObj execStats = shardResults[i].result["executionStats"].Obj(); if (execStats.hasField("nReturned")) { nReturned += execStats["nReturned"].numberLong(); } if (execStats.hasField("totalKeysExamined")) { keysExamined += execStats["totalKeysExamined"].numberLong(); } if (execStats.hasField("totalDocsExamined")) { docsExamined += execStats["totalDocsExamined"].numberLong(); } if (execStats.hasField("executionTimeMillis")) { totalChildMillis += execStats["executionTimeMillis"].numberLong(); } } // Fill in top-level stats. executionStatsBob.appendNumber("nReturned", nReturned); executionStatsBob.appendNumber("executionTimeMillis", millisElapsed); executionStatsBob.appendNumber("totalKeysExamined", keysExamined); executionStatsBob.appendNumber("totalDocsExamined", docsExamined); // Fill in the tree of stages. BSONObjBuilder executionStagesBob( executionStatsBob.subobjStart("executionStages")); // Info for the root mongos stage. executionStagesBob.append("stage", mongosStageName); executionStatsBob.appendNumber("nReturned", nReturned); executionStatsBob.appendNumber("executionTimeMillis", millisElapsed); executionStatsBob.appendNumber("totalKeysExamined", keysExamined); executionStatsBob.appendNumber("totalDocsExamined", docsExamined); executionStagesBob.append("totalChildMillis", totalChildMillis); BSONArrayBuilder execShardsBuilder(executionStagesBob.subarrayStart("shards")); for (size_t i = 0; i < shardResults.size(); i++) { BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart()); BSONObj execStats = shardResults[i].result["executionStats"].Obj(); BSONObj execStages = execStats["executionStages"].Obj(); singleShardBob.append("shardName", shardResults[i].shardTarget.getName()); // Append error-related fields, if present. if (!execStats["executionSuccess"].eoo()) { singleShardBob.append(execStats["executionSuccess"]); } if (!execStats["errorMessage"].eoo()) { singleShardBob.append(execStats["errorMessage"]); } if (!execStats["errorCode"].eoo()) { singleShardBob.append(execStats["errorCode"]); } appendIfRoom(&singleShardBob, execStages, "executionStages"); singleShardBob.doneFast(); } execShardsBuilder.doneFast(); executionStagesBob.doneFast(); if (!shardResults[0].result["executionStats"].Obj().hasField("allPlansExecution")) { // The shards don't have execution stats for all plans, so we're done. executionStatsBob.doneFast(); return; } // Add the allPlans stats from each shard. BSONArrayBuilder allPlansExecBob( executionStatsBob.subarrayStart("allPlansExecution")); for (size_t i = 0; i < shardResults.size(); i++) { BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart()); singleShardBob.append("shardName", shardResults[i].shardTarget.getName()); BSONObj execStats = shardResults[i].result["executionStats"].Obj(); vector allPlans = execStats["allPlansExecution"].Array(); BSONArrayBuilder innerArrayBob(singleShardBob.subarrayStart("allPlans")); for (size_t j = 0; j < allPlans.size(); j++) { appendToArrayIfRoom(&innerArrayBob, allPlans[j]); } innerArrayBob.done(); singleShardBob.doneFast(); } allPlansExecBob.doneFast(); executionStatsBob.doneFast(); } // static Status ClusterExplain::buildExplainResult(const vector& shardResults, const char* mongosStageName, long long millisElapsed, BSONObjBuilder* out) { // Explain only succeeds if all shards support the explain command. Status validateStatus = ClusterExplain::validateShardResults(shardResults); if (!validateStatus.isOK()) { return validateStatus; } buildPlannerInfo(shardResults, mongosStageName, out); buildExecStats(shardResults, mongosStageName, millisElapsed, out); return Status::OK(); } } // namespace mongo