diff options
author | Anton Korshunov <anton.korshunov@mongodb.com> | 2019-06-17 15:47:18 +0100 |
---|---|---|
committer | Anton Korshunov <anton.korshunov@mongodb.com> | 2019-06-19 17:08:11 +0100 |
commit | 46e086d2093798cdec949eba2919ccac88719166 (patch) | |
tree | 65a3be4c30cfbb7984277d1acc30d5834c4b29c6 | |
parent | c5eaeddb0f0273c5843d0ca3d742970c45749d0c (diff) | |
download | mongo-46e086d2093798cdec949eba2919ccac88719166.tar.gz |
SERVER-33967 Include executionTimeMillis for each shard in the explain output
3 files changed, 139 insertions, 16 deletions
diff --git a/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml b/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml index 039bd4a42ff..dec06467969 100644 --- a/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml +++ b/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml @@ -134,6 +134,7 @@ selector: # Enable if SERVER-36966 is backported or 4.2 becomes last-stable - jstests/sharding/mr_output_sharded_validation.js # Enable when 4.4 becomes last stable + - jstests/sharding/explain_exec_stats_on_shards.js - jstests/sharding/refine_collection_shard_key_basic.js # Enable when SERVER-41237 is backported - jstests/sharding/graph_lookup.js diff --git a/jstests/sharding/explain_exec_stats_on_shards.js b/jstests/sharding/explain_exec_stats_on_shards.js new file mode 100644 index 00000000000..d9c9067f3ef --- /dev/null +++ b/jstests/sharding/explain_exec_stats_on_shards.js @@ -0,0 +1,137 @@ +// Tests for the mongos explain command to ensure that the 'executionStats' section of the explain +// output is populated correctly for each shard. +(function() { + 'use strict'; + + // Verifies that the explain output for the given shard contains all expected fields and that + // field values match the values specified in the 'expected*' arguments. This function also + // updates the 'totals' object which holds accumulated values for certain fields from each + // shard. + function verifyExecStatsOnShard({explain, + expectedShardName, + expectedNReturned, + expectedKeysExamined, + expectedDocsExamined, + totals}) { + assert(explain.executionSuccess, tojson(explain)); + assert.eq(explain.shardName, expectedShardName, tojson(explain)); + assert.eq(explain.nReturned, expectedNReturned, tojson(explain)); + assert.gte(explain.executionTimeMillis, 0, tojson(explain)); + assert.eq(explain.totalKeysExamined, expectedKeysExamined, tojson(explain)); + assert.eq(explain.totalDocsExamined, expectedDocsExamined, tojson(explain)); + assert(explain.hasOwnProperty("executionStages"), tojson(explain)); + + totals.nReturned += explain.nReturned; + totals.executionTimeMillis += explain.executionTimeMillis; + totals.keysExamined += explain.totalKeysExamined; + totals.docsExamined += explain.totalDocsExamined; + } + + // Create a cluster with 2 shards. + const numShards = 2; + const st = new ShardingTest({shards: numShards}); + const db = st.s.getDB(`${jsTest.name()}_db`); + + // Enable sharding on the database and use shard0 as the primary shard. + assert.commandWorked(db.adminCommand({enableSharding: db.getName()})); + st.ensurePrimaryShard(db.getName(), st.shard0.shardName); + + // Test that the explain's 'executionStats' section includes all relevant fields for each shard + // when the 'explain' command is executed against a sharded collection. + (function testExplainExecutionStatsWithShardedCollection() { + // Set up a collection, shard on {a:1}, split at {a:4}, and move the {a:4} chunk to shard1. + const shardedColl = db.getCollection(`${jsTest.name()}_sharded`); + shardedColl.drop(); + st.shardColl(shardedColl, {a: 1}, {a: 4}, {a: 4}); + + // Put documents on each shard. + const numDocs = 10; + for (let i = 0; i < numDocs; i++) { + assert.commandWorked(shardedColl.insert({_id: i, a: i})); + } + assert.eq(shardedColl.find().itcount(), numDocs); + + // Explain the find command and check that all expected explain fields are present in the + // output. + const explain = shardedColl.find({}).explain("executionStats"); + assert(explain.hasOwnProperty("executionStats"), tojson(explain)); + assert(explain.executionStats.hasOwnProperty("executionStages"), tojson(explain)); + assert(explain.executionStats.executionStages.hasOwnProperty("shards"), tojson(explain)); + assert.eq(explain.executionStats.executionStages.shards.length, numShards); + + // Verify execution stats on each shards and accumulate totals. + const totals = {nReturned: 0, executionTimeMillis: 0, keysExamined: 0, docsExamined: 0}; + const executionStages = explain.executionStats.executionStages; + // Sort the shards array by the 'shardName' to guarantee consistent results, as explain + // outputs from the shards may arrive in an arbitrary order. + executionStages.shards.sort((a, b) => a.shardName.localeCompare(b.shardName)); + verifyExecStatsOnShard({ + explain: executionStages.shards[0], + expectedShardName: st.shard0.shardName, + expectedNReturned: 4, + expectedKeysExamined: 0, + expectedDocsExamined: 4, + totals: totals + }); + verifyExecStatsOnShard({ + explain: executionStages.shards[1], + expectedShardName: st.shard1.shardName, + expectedNReturned: 6, + expectedKeysExamined: 0, + expectedDocsExamined: 6, + totals: totals + }); + + // Ensure that execution stats accumulated across all shards matches the values in the + // top-level 'executionStages' section of the explain output. + assert.eq(executionStages.nReturned, totals.nReturned, tojson(explain)); + assert.eq(executionStages.totalChildMillis, totals.executionTimeMillis, tojson(explain)); + assert.eq(executionStages.totalKeysExamined, totals.keysExamined, tojson(explain)); + assert.eq(executionStages.totalDocsExamined, totals.docsExamined, tojson(explain)); + })(); + + // Test that the explain's 'executionStats' section includes all relevant fields when the + // 'explain' command is executed against an unsharded collection. + (function testExplainExecutionStatsWithUnshardedCollection() { + // Setup an unsharded collection. + const unshardedColl = db.getCollection(`${jsTest.name()}_unsharded`); + unshardedColl.drop(); + assert.commandWorked(unshardedColl.ensureIndex({a: 1})); + + // Add documents to the collection. + const numDocs = 10; + for (let i = 0; i < numDocs; i++) { + assert.commandWorked(unshardedColl.insert({_id: i, a: i})); + } + assert.eq(unshardedColl.count(), numDocs); + + // Explain the find command and check that all expected explain fields are present in the + // output. + const explain = unshardedColl.find({}).explain("executionStats"); + assert(explain.hasOwnProperty("executionStats"), tojson(explain)); + assert(explain.executionStats.hasOwnProperty("executionStages"), tojson(explain)); + assert(explain.executionStats.executionStages.hasOwnProperty("shards"), tojson(explain)); + assert.eq(explain.executionStats.executionStages.shards.length, 1); + + // Verify execution stats on the primary shard and accumulate totals. + const totals = {nReturned: 0, executionTimeMillis: 0, keysExamined: 0, docsExamined: 0}; + const executionStages = explain.executionStats.executionStages; + verifyExecStatsOnShard({ + explain: executionStages.shards[0], + expectedShardName: st.shard0.shardName, + expectedNReturned: 10, + expectedKeysExamined: 0, + expectedDocsExamined: 10, + totals: totals + }); + + // Ensure that execution stats on the primary shard matches the values in the top-level + // 'executionStages' section of the explain output. + assert.eq(executionStages.nReturned, totals.nReturned, tojson(explain)); + assert.eq(executionStages.totalChildMillis, totals.executionTimeMillis, tojson(explain)); + assert.eq(executionStages.totalKeysExamined, totals.keysExamined, tojson(explain)); + assert.eq(executionStages.totalDocsExamined, totals.docsExamined, tojson(explain)); + })(); + + st.stop(); +})(); diff --git a/src/mongo/s/commands/cluster_explain.cpp b/src/mongo/s/commands/cluster_explain.cpp index d814f6080d9..fb3b748f4c5 100644 --- a/src/mongo/s/commands/cluster_explain.cpp +++ b/src/mongo/s/commands/cluster_explain.cpp @@ -304,25 +304,10 @@ void ClusterExplain::buildExecStats(const vector<Strategy::CommandResult>& shard 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].shardTargetId.toString()); - - // 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"); - + appendElementsIfRoom(&singleShardBob, execStats); singleShardBob.doneFast(); } execShardsBuilder.doneFast(); |