diff options
-rw-r--r-- | jstests/core/currentop_cursors.js | 31 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.h | 11 | ||||
-rw-r--r-- | src/mongo/db/generic_cursor.idl | 4 | ||||
-rw-r--r-- | src/mongo/db/pipeline/mongo_process_common.cpp | 20 |
5 files changed, 66 insertions, 5 deletions
diff --git a/jstests/core/currentop_cursors.js b/jstests/core/currentop_cursors.js index be8afeca54c..f46d7c82f84 100644 --- a/jstests/core/currentop_cursors.js +++ b/jstests/core/currentop_cursors.js @@ -8,6 +8,9 @@ (function() { "use strict"; const coll = db.jstests_currentop_cursors; + + load("jstests/libs/fixture_helpers.js"); // for FixtureHelpers + // Avoiding using the shell helper to avoid the implicit collection recreation. db.runCommand({drop: coll.getName()}); assert.commandWorked(db.createCollection(coll.getName(), {capped: true, size: 1000})); @@ -63,6 +66,12 @@ }, assertFunc: function(cursorId, result) { assert.eq(result.length, 1, result); + // Plan summary does not exist on mongos, so skip this test on mongos. + if (!FixtureHelpers.isMongos(db)) { + assert.eq(result[0].planSummary, "COLLSCAN", result); + } else { + assert(!result[0].hasOwnProperty("planSummary"), result); + } const idleCursor = result[0].cursor; assert.eq(idleCursor.nDocsReturned, 2, result); assert.eq(idleCursor.nBatchesReturned, 1, result); @@ -71,6 +80,7 @@ assert.eq(idleCursor.noCursorTimeout, false, result); assert.eq(idleCursor.originatingCommand.batchSize, 2, result); assert.lte(idleCursor.createdDate, idleCursor.lastAccessDate, result); + assert(!idleCursor.hasOwnProperty("planSummary"), result); } }); runTest({ @@ -189,4 +199,25 @@ } }); + // planSummary does not exist on Mongos, so skip this test. + if (!FixtureHelpers.isMongos(db)) { + runTest({ + findFunc: function() { + assert.commandWorked(coll.createIndex({"val": 1})); + return assert + .commandWorked(db.runCommand({ + find: "jstests_currentop_cursors", + filter: {"val": {$gt: 2}}, + batchSize: 2 + })) + .cursor.id; + + }, + assertFunc: function(cursorId, result) { + assert.eq(result.length, 1, result); + assert.eq(result[0].planSummary, "IXSCAN { val: 1 }", result); + + } + }); + } })(); diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 756f1fbc7eb..92e6a1ffb30 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -47,6 +47,7 @@ #include "mongo/db/commands/server_status_metric.h" #include "mongo/db/cursor_server_params.h" #include "mongo/db/jsobj.h" +#include "mongo/db/query/explain.h" #include "mongo/db/repl/repl_client_info.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/util/background.h" @@ -92,7 +93,8 @@ ClientCursor::ClientCursor(ClientCursorParams params, _exec(std::move(params.exec)), _operationUsingCursor(operationUsingCursor), _lastUseDate(now), - _createdDate(now) { + _createdDate(now), + _planSummary(Explain::getPlanSummary(_exec.get())) { invariant(_cursorManager); invariant(_exec); invariant(_operationUsingCursor); @@ -143,6 +145,7 @@ GenericCursor ClientCursor::toGenericCursor() const { gc.setLastAccessDate(getLastUseDate()); gc.setCreatedDate(getCreatedDate()); gc.setNBatchesReturned(getNBatches()); + gc.setPlanSummary(getPlanSummary()); if (auto opCtx = _operationUsingCursor) { gc.setOperationUsingCursorId(opCtx->getOpID()); } diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 96cae17da5b..83b326b93da 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -209,6 +209,14 @@ public: return _createdDate; } + void setPlanSummary(std::string ps) { + _planSummary = std::move(ps); + } + + StringData getPlanSummary() const { + return StringData(_planSummary); + } + /** * Returns a generic cursor containing diagnostics about this cursor. * The caller must either have this cursor pinned or hold a mutex from the cursor manager. @@ -374,6 +382,9 @@ private: Date_t _lastUseDate; Date_t _createdDate; + + // A string with the plan summary of the cursor's query. + std::string _planSummary; }; /** diff --git a/src/mongo/db/generic_cursor.idl b/src/mongo/db/generic_cursor.idl index 24661fa9221..2792a9db88e 100644 --- a/src/mongo/db/generic_cursor.idl +++ b/src/mongo/db/generic_cursor.idl @@ -71,6 +71,10 @@ structs: lsid: type: LogicalSessionId optional: true + planSummary: + description: The plan summary of this cursor's query. + type: string + optional: true operationUsingCursorId: description: The op ID of the operation pinning the cursor. Will be empty for idle cursors. type: long diff --git a/src/mongo/db/pipeline/mongo_process_common.cpp b/src/mongo/db/pipeline/mongo_process_common.cpp index aae991ba05f..1c97921e237 100644 --- a/src/mongo/db/pipeline/mongo_process_common.cpp +++ b/src/mongo/db/pipeline/mongo_process_common.cpp @@ -78,10 +78,22 @@ std::vector<BSONObj> MongoProcessCommon::getCurrentOps( if (cursorMode == CurrentOpCursorMode::kIncludeCursors) { for (auto&& cursor : getIdleCursors(expCtx, userMode)) { - ops.push_back(BSON("type" - << "idleCursor" - << "cursor" - << cursor.toBSON())); + BSONObjBuilder cursorObj; + cursorObj.append("type", "idleCursor"); + // On mongos, planSummary is not present. + auto planSummaryData = cursor.getPlanSummary(); + if (planSummaryData) { + auto planSummaryText = planSummaryData->toString(); + // Plan summary has to appear in the top level object, not the cursor object. + // We remove it, create the op, then put it back. + cursor.setPlanSummary(boost::none); + cursorObj.append("planSummary", planSummaryText); + cursorObj.append("cursor", cursor.toBSON()); + cursor.setPlanSummary(StringData(planSummaryText)); + } else { + cursorObj.append("cursor", cursor.toBSON()); + } + ops.emplace_back(cursorObj.obj()); } } |