diff options
author | Mindaugas Malinauskas <mindaugas.malinauskas@mongodb.com> | 2020-07-14 14:25:40 +0300 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-10-13 12:21:50 +0000 |
commit | 392e8c8ffbe242805bf5a82437d0ae39d1fc6a8a (patch) | |
tree | f39d309449b2296a7e9c41a4e40c83a73a2bad04 | |
parent | 5454f3bf6391624e42efbc2538536cb0e8bdaab2 (diff) | |
download | mongo-392e8c8ffbe242805bf5a82437d0ae39d1fc6a8a.tar.gz |
SERVER-44586 add metrics to serverStatus to track type of update command
(cherry picked from commit d54afba70a6f028dd68878c03228091bea476176)
(cherry picked from commit 6769d311805862cd7271934dc3f5be1ce974a9a1)
19 files changed, 410 insertions, 11 deletions
diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_100ms_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_100ms_refresh_jscore_passthrough.yml index 5a57622d8de..743c0f0c059 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_100ms_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_100ms_refresh_jscore_passthrough.yml @@ -24,6 +24,10 @@ selector: # time, the logical session cache refresh thread will flush these sessions to disk, creating more # opLog entries. To avoid this infinite loop, we will blacklist the test from this suite. - jstests/core/awaitdata_getmore_cmd.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_10sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_10sec_refresh_jscore_passthrough.yml index adb75e752fe..0e69dd724c1 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_10sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_10sec_refresh_jscore_passthrough.yml @@ -24,6 +24,10 @@ selector: # time, the logical session cache refresh thread will flush these sessions to disk, creating more # opLog entries. To avoid this infinite loop, we will blacklist the test from this suite. - jstests/core/awaitdata_getmore_cmd.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_1sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_1sec_refresh_jscore_passthrough.yml index 88631341ded..617ad0f95f6 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_1sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_1sec_refresh_jscore_passthrough.yml @@ -24,6 +24,10 @@ selector: # time, the logical session cache refresh thread will flush these sessions to disk, creating more # opLog entries. To avoid this infinite loop, we will blacklist the test from this suite. - jstests/core/awaitdata_getmore_cmd.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_default_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_default_refresh_jscore_passthrough.yml index fabca31780d..49dde5b8a15 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_replication_default_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_replication_default_refresh_jscore_passthrough.yml @@ -24,6 +24,10 @@ selector: # time, the logical session cache refresh thread will flush these sessions to disk, creating more # opLog entries. To prevent this infinite loop, we will blacklist the test from this suite. - jstests/core/awaitdata_getmore_cmd.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_100ms_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_100ms_refresh_jscore_passthrough.yml index 66581ae753d..c8314a047a8 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_100ms_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_100ms_refresh_jscore_passthrough.yml @@ -15,6 +15,10 @@ selector: - jstests/core/list_all_sessions.js - jstests/core/list_local_sessions.js - jstests/core/list_sessions.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_10sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_10sec_refresh_jscore_passthrough.yml index 1f390766cad..0b06a2c3d68 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_10sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_10sec_refresh_jscore_passthrough.yml @@ -15,6 +15,10 @@ selector: - jstests/core/list_all_sessions.js - jstests/core/list_local_sessions.js - jstests/core/list_sessions.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_1sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_1sec_refresh_jscore_passthrough.yml index 60f42e3549b..79b860c743f 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_1sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_1sec_refresh_jscore_passthrough.yml @@ -15,6 +15,10 @@ selector: - jstests/core/list_all_sessions.js - jstests/core/list_local_sessions.js - jstests/core/list_sessions.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_default_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_default_refresh_jscore_passthrough.yml index 8956998ae00..74a6a55b1f6 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_default_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_standalone_default_refresh_jscore_passthrough.yml @@ -15,6 +15,10 @@ selector: - jstests/core/list_all_sessions.js - jstests/core/list_local_sessions.js - jstests/core/list_sessions.js + # These tests verify that an expected number of update operations were tracked in the server + # status metrics, but the logical session cache refresh causes additional updates to be recorded. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js executor: archive: diff --git a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml index f39dbe7bbf5..7b11c21181e 100644 --- a/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml @@ -40,6 +40,10 @@ selector: # TODO SERVER-31198: Remove once retry attempts are always decremented. - jstests/core/write_result.js + # These tests rely on the assumption that an update command is run only once. + - jstests/core/find_and_modify_metrics.js + - jstests/core/update_metrics.js + # This test makes the assumption that a command is run a certain number of times, but # the retryable writes suite overrides the runCommand to repeat commands. - jstests/core/failcommand_failpoint.js diff --git a/jstests/core/find_and_modify_metrics.js b/jstests/core/find_and_modify_metrics.js new file mode 100644 index 00000000000..4fb500b81f0 --- /dev/null +++ b/jstests/core/find_and_modify_metrics.js @@ -0,0 +1,78 @@ +/** + * Tests "metrics.commands.findAndModify.pipeline" and "metrics.commands.findAndModify.arrayFilters" + * counters of the findAndModify command. + * + * @tags: [ + * requires_fcv_42, + * # The test relies on the precise number of executions of commands. + * requires_non_retryable_writes, + * # The test is designed to work with an unsharded collection. + * assumes_unsharded_collection, + * ] + */ +(function() { +"use strict"; + +const testDB = db.getSiblingDB(jsTestName()); +assert.commandWorked(testDB.dropDatabase()); +const coll = testDB.findAndModify_metrics; +assert.commandWorked(testDB.createCollection(coll.getName())); + +assert.commandWorked(coll.insert([{key: 1, value: 1, array: [5, 10]}])); + +// "Initialize" the counters for the findAndModify command. +let result = coll.findAndModify({query: {key: 1}, update: {$set: {value: 0}}}); +assert.eq(1, result.key); + +let serverStatusBeforeTest = testDB.serverStatus(); + +// Verify that the metrics.commands.findAndModify.pipeline counter is present. +assert.gte(serverStatusBeforeTest.metrics.commands.findAndModify.pipeline, + 0, + tojson(serverStatusBeforeTest)); + +// Verify that that findAndModify command without aggregation pipeline-style update does not +// increment the counter. +result = coll.findAndModify({query: {key: 1}, update: {$set: {value: 5}}}); +assert.eq(1, result.key); +let serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.findAndModify.pipeline, + serverStatusAfterTest.metrics.commands.findAndModify.pipeline, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +// Verify that that findAndModify command with aggregation pipeline-style update increments the +// counter. +result = coll.findAndModify({query: {key: 1}, update: [{$set: {value: 10}}]}); +assert.eq(1, result.key); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.findAndModify.pipeline + 1, + serverStatusAfterTest.metrics.commands.findAndModify.pipeline, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +serverStatusBeforeTest = testDB.serverStatus(); + +// Verify that the metrics.commands.findAndModify.arrayFilters counter is present. +assert.gte(serverStatusBeforeTest.metrics.commands.findAndModify.arrayFilters, + 0, + tojson(serverStatusBeforeTest)); + +// Verify that that findAndModify command without arrayFilters does not increment the counter. +result = coll.findAndModify({query: {key: 1}, update: {$set: {value: 5}}}); +assert.eq(1, result.key); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.findAndModify.arrayFilters, + serverStatusAfterTest.metrics.commands.findAndModify.arrayFilters, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +// Verify that that findAndModify command with arrayFilters increments the counter. +result = coll.findAndModify({ + query: {key: 1}, + update: {$set: {"array.$[element]": 20}}, + arrayFilters: [{"element": {$gt: 6}}] +}); +assert.eq(1, result.key); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.findAndModify.arrayFilters + 1, + serverStatusAfterTest.metrics.commands.findAndModify.arrayFilters, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); +})();
\ No newline at end of file diff --git a/jstests/core/update_metrics.js b/jstests/core/update_metrics.js new file mode 100644 index 00000000000..e046b0136e5 --- /dev/null +++ b/jstests/core/update_metrics.js @@ -0,0 +1,79 @@ +/** + * Tests "metrics.commands.update.pipeline" and "metrics.commands.update.arrayFilters" counters of + * the update command. + * + * @tags: [ + * requires_fcv_42, + * # The test relies on the precise number of executions of commands. + * requires_non_retryable_writes, + * # The test is designed to work with an unsharded collection. + * assumes_unsharded_collection, + * # The coll.update command does not work with $set operator in compatibility write mode. + * requires_find_command, + * ] + */ +(function() { +"use strict"; + +const testDB = db.getSiblingDB(jsTestName()); +assert.commandWorked(testDB.dropDatabase()); +const coll = testDB.update_metrics; +assert.commandWorked(testDB.createCollection(coll.getName())); + +assert.commandWorked(coll.insert([{key: 1, value: 1, array: [5, 10]}])); + +// "Initialize" the counters for the update command. +assert.commandWorked(coll.update({key: 1}, {$set: {value: 0}})); + +let serverStatusBeforeTest = testDB.serverStatus(); + +// Verify that the metrics.commands.update.pipeline counter is present. +assert.gte( + serverStatusBeforeTest.metrics.commands.update.pipeline, 0, tojson(serverStatusBeforeTest)); + +// Verify that that update command without aggregation pipeline-style update does not increment the +// counter. +assert.commandWorked(coll.update({key: 1}, {$set: {value: 5}})); +let serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.update.pipeline, + serverStatusAfterTest.metrics.commands.update.pipeline, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +// Verify that that update command with aggregation pipeline-style update increments the counter. +assert.commandWorked(coll.update({key: 1}, [{$set: {value: 10}}])); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.update.pipeline + 1, + serverStatusAfterTest.metrics.commands.update.pipeline, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +serverStatusBeforeTest = testDB.serverStatus(); + +// Verify that the metrics.commands.update.arrayFilters counter is present. +assert.gte( + serverStatusBeforeTest.metrics.commands.update.arrayFilters, 0, tojson(serverStatusBeforeTest)); + +// Verify that that update command without arrayFilters does not increment the counter. +assert.commandWorked(coll.update({key: 1}, {$set: {value: 5}})); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.update.arrayFilters, + serverStatusAfterTest.metrics.commands.update.arrayFilters, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +// Verify that that update command with arrayFilters increments the counter. +assert.commandWorked(coll.update( + {key: 1}, {$set: {"array.$[element]": 20}}, {arrayFilters: [{"element": {$gt: 6}}]})); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.update.arrayFilters + 1, + serverStatusAfterTest.metrics.commands.update.arrayFilters, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); + +// Verify that that a multi-document update command with arrayFilters increments the counter. +assert.commandWorked( + coll.insert([{key: 2, value: 1, array: [7, 0]}, {key: 3, value: 1, array: [7, 0]}])); +assert.commandWorked(coll.update( + {}, {$set: {"array.$[element]": 20}}, {multi: true, arrayFilters: [{"element": {$gt: 6}}]})); +serverStatusAfterTest = testDB.serverStatus(); +assert.eq(serverStatusBeforeTest.metrics.commands.update.arrayFilters + 2, + serverStatusAfterTest.metrics.commands.update.arrayFilters, + `Before: ${tojson(serverStatusBeforeTest)}, after: ${tojson(serverStatusAfterTest)}`); +})();
\ No newline at end of file diff --git a/jstests/libs/parallelTester.js b/jstests/libs/parallelTester.js index bd96e8aef61..5628b232abb 100644 --- a/jstests/libs/parallelTester.js +++ b/jstests/libs/parallelTester.js @@ -201,6 +201,10 @@ if (typeof _threadInject != "undefined") { // Assumes that other tests are not creating cursors. "kill_cursors.js", + // These tests check global command counters. + "find_and_modify_metrics.js", + "update_metrics.js", + // Views tests "views/invalid_system_views.js", // Puts invalid view definitions in system.views. "views/views_all_commands.js", // Drops test DB. diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 04c15212877..e7bf9e16d93 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -106,6 +106,7 @@ env.Library( 'refresh_sessions_command.cpp', 'rename_collection_common.cpp', 'start_session_command.cpp', + 'update_metrics.cpp', env.Idlc('parameters.idl')[0], env.Idlc('sessions_commands.idl')[0], ], @@ -126,6 +127,7 @@ env.Library( '$BUILD_DIR/mongo/logger/parse_log_component_settings', '$BUILD_DIR/mongo/rpc/protocol', 'test_commands_enabled', + 'server_status_core', ], ) diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index 8f5ce4827b6..925299a5166 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -41,6 +41,7 @@ #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/commands/find_and_modify_common.h" +#include "mongo/db/commands/update_metrics.h" #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/db_raii.h" #include "mongo/db/exec/delete.h" @@ -204,7 +205,8 @@ void checkIfTransactionOnCappedColl(Collection* coll, bool inTransaction) { class CmdFindAndModify : public BasicCommand { public: - CmdFindAndModify() : BasicCommand("findAndModify", "findandmodify") {} + CmdFindAndModify() + : BasicCommand("findAndModify", "findandmodify"), _updateMetrics{"findAndModify"} {} std::string help() const override { return "{ findAndModify: \"collection\", query: {processed:false}, update: {$set: " @@ -318,6 +320,9 @@ public: auto const curOp = CurOp::get(opCtx); OpDebug* const opDebug = &curOp->debug(); + // Collect metrics. + _updateMetrics.collectMetrics(cmdObj); + boost::optional<DisableDocumentValidation> maybeDisableValidation; if (shouldBypassDocumentValidationForCommand(cmdObj)) { maybeDisableValidation.emplace(opCtx); @@ -523,6 +528,9 @@ public: }); } +private: + // Update related command execution metrics. + UpdateMetrics _updateMetrics; } cmdFindAndModify; } // namespace diff --git a/src/mongo/db/commands/update_metrics.cpp b/src/mongo/db/commands/update_metrics.cpp new file mode 100644 index 00000000000..e1a2aadbfc1 --- /dev/null +++ b/src/mongo/db/commands/update_metrics.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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/db/commands/update_metrics.h" + +namespace mongo { +UpdateMetrics::UpdateMetrics(StringData commandName) + : _commandsWithAggregationPipelineMetric("commands." + commandName + ".pipeline", + &_commandsWithAggregationPipeline), + _commandsWithArrayFiltersMetric("commands." + commandName + ".arrayFilters", + &_commandsWithArrayFilters) {} + +void UpdateMetrics::incrementExecutedWithAggregationPipeline() { + _commandsWithAggregationPipeline.increment(); +} + +void UpdateMetrics::incrementExecutedWithArrayFilters() { + _commandsWithArrayFilters.increment(); +} + +void UpdateMetrics::collectMetrics(const BSONObj& cmdObj) { + // If this command is a pipeline-style update, record that it was used. + if (cmdObj.hasField("update") && (cmdObj.getField("update").type() == BSONType::Array)) { + _commandsWithAggregationPipeline.increment(); + } + + // If this command had arrayFilters option, record that it was used. + if (cmdObj.hasField("arrayFilters")) { + _commandsWithArrayFilters.increment(); + } +} +} // namespace mongo diff --git a/src/mongo/db/commands/update_metrics.h b/src/mongo/db/commands/update_metrics.h new file mode 100644 index 00000000000..62a78ea94ee --- /dev/null +++ b/src/mongo/db/commands/update_metrics.h @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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. + */ + +#pragma once + +#include "mongo/base/counter.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/commands/server_status_metric.h" + +namespace mongo { +/** + * Execution metrics for update type commands - update, findAndModify and their mongoS variants. + * Exposes the metrics to the output of the serverStatus command. + */ +class UpdateMetrics { +public: + /** + * Construct metrics for a command identified by commandName. + */ + UpdateMetrics(StringData commandName); + + /** + * Increment counter for how many times this command has executed with an aggregation + * pipeline-style update parameter. + */ + void incrementExecutedWithAggregationPipeline(); + + /** + * Increment counter for how many times this command has executed with the arrayFilters option. + */ + void incrementExecutedWithArrayFilters(); + + /** + * Collect update related metrics from the command object for update type commands. Update type + * commands may have update and arrayFilters fields. Accepts an unparsed command object to + * support use cases when the command object is not fully parsed by the command. + */ + void collectMetrics(const BSONObj& cmdObj); + +private: + // A counter for how many times this command has been executed with an aggregation + // pipeline-style update parameter. + Counter64 _commandsWithAggregationPipeline; + + // A counter for how many times this command has been executed with the arrayFilters option. + Counter64 _commandsWithArrayFilters; + + // A server metric field for the command executions with an aggregation pipeline-style update + // parameter counter. + ServerStatusMetricField<Counter64> _commandsWithAggregationPipelineMetric; + + // A server metric field for the command executions with arrayFilters option counter. + ServerStatusMetricField<Counter64> _commandsWithArrayFiltersMetric; +}; +} // namespace mongo diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index e70ca6595ac..fea06036119 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -34,6 +34,7 @@ #include "mongo/db/catalog/document_validation.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" +#include "mongo/db/commands/update_metrics.h" #include "mongo/db/commands/write_commands/write_commands_common.h" #include "mongo/db/curop.h" #include "mongo/db/db_raii.h" @@ -325,13 +326,19 @@ private: class CmdUpdate final : public WriteCommand { public: - CmdUpdate() : WriteCommand("update") {} + CmdUpdate() : WriteCommand("update"), _updateMetrics{"update"} {} private: class Invocation final : public InvocationBase { public: - Invocation(const WriteCommand* cmd, const OpMsgRequest& request) - : InvocationBase(cmd, request), _batch(UpdateOp::parse(request)) {} + Invocation(const WriteCommand* cmd, + const OpMsgRequest& request, + UpdateMetrics* updateMetrics) + : InvocationBase(cmd, request), + _batch(UpdateOp::parse(request)), + _updateMetrics{updateMetrics} { + invariant(_updateMetrics); + } private: NamespaceString ns() const override { @@ -350,14 +357,24 @@ private: _batch.getUpdates().size(), std::move(reply), &result); + + // Collect metrics. auto updates = _batch.getUpdates(); for (auto&& update : updates) { + // If this was a pipeline style update, record that pipeline-style was used and + // which stages were being used. auto& updateMod = update.getU(); if (updateMod.type() == write_ops::UpdateModification::Type::kPipeline) { AggregationRequest request(_batch.getNamespace(), updateMod.getUpdatePipeline()); LiteParsedPipeline pipeline(request); pipeline.tickGlobalStageCounters(); + _updateMetrics->incrementExecutedWithAggregationPipeline(); + } + + // If this command had arrayFilters option, record that it was used. + if (update.getArrayFilters()) { + _updateMetrics->incrementExecutedWithArrayFilters(); } } } @@ -401,10 +418,13 @@ private: } write_ops::Update _batch; + + // Update related command execution metrics. + UpdateMetrics* const _updateMetrics; }; std::unique_ptr<CommandInvocation> parse(OperationContext*, const OpMsgRequest& request) { - return stdx::make_unique<Invocation>(this, request); + return stdx::make_unique<Invocation>(this, request, &_updateMetrics); } void snipForLogging(mutablebson::Document* cmdObj) const final { @@ -414,6 +434,9 @@ private: std::string help() const final { return "update documents"; } + + // Update related command execution metrics. + UpdateMetrics _updateMetrics; } cmdUpdate; class CmdDelete final : public WriteCommand { diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp index 8f407bbd9b3..e5d86c59f8a 100644 --- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp @@ -34,6 +34,7 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" #include "mongo/db/commands/find_and_modify_common.h" +#include "mongo/db/commands/update_metrics.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/storage/duplicate_key_error_info.h" #include "mongo/executor/task_executor_pool.h" @@ -139,7 +140,8 @@ void updateShardKeyValueOnWouldChangeOwningShardError(OperationContext* opCtx, class FindAndModifyCmd : public BasicCommand { public: - FindAndModifyCmd() : BasicCommand("findAndModify", "findandmodify") {} + FindAndModifyCmd() + : BasicCommand("findAndModify", "findandmodify"), _updateMetrics{"findAndModify"} {} AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { return AllowedOnSecondary::kAlways; @@ -226,6 +228,9 @@ public: BSONObjBuilder& result) override { const NamespaceString nss(CommandHelpers::parseNsCollectionRequired(dbName, cmdObj)); + // Collect metrics. + _updateMetrics.collectMetrics(cmdObj); + // findAndModify should only be creating database if upsert is true, but this would require // that the parsing be pulled into this function. createShardDatabase(opCtx, nss.db()); @@ -356,6 +361,9 @@ private: result->appendElementsUnique( CommandHelpers::filterCommandReplyForPassthrough(response.data)); } + + // Update related command execution metrics. + UpdateMetrics _updateMetrics; } findAndModifyCmd; } // namespace diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp index 75649cb1775..0be5d7c0af4 100644 --- a/src/mongo/s/commands/cluster_write_cmd.cpp +++ b/src/mongo/s/commands/cluster_write_cmd.cpp @@ -36,6 +36,7 @@ #include "mongo/client/remote_command_targeter.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/commands.h" +#include "mongo/db/commands/update_metrics.h" #include "mongo/db/commands/write_commands/write_commands_common.h" #include "mongo/db/curop.h" #include "mongo/db/lasterror.h" @@ -421,11 +422,13 @@ class ClusterWriteCmd::InvocationBase : public CommandInvocation { public: InvocationBase(const ClusterWriteCmd* command, const OpMsgRequest& request, - BatchedCommandRequest batchedRequest) + BatchedCommandRequest batchedRequest, + UpdateMetrics* updateMetrics = nullptr) : CommandInvocation(command), _bypass{shouldBypassDocumentValidationForCommand(request.body)}, _request{&request}, - _batchedRequest{std::move(batchedRequest)} {} + _batchedRequest{std::move(batchedRequest)}, + _updateMetrics{updateMetrics} {} const BatchedCommandRequest& getBatchedRequest() const { return _batchedRequest; @@ -494,14 +497,23 @@ private: debug.additiveMetrics.nMatched = response.getN() - (debug.upsert ? response.sizeUpsertDetails() : 0); debug.additiveMetrics.nModified = response.getNModified(); + + invariant(_updateMetrics); for (auto&& update : _batchedRequest.getUpdateRequest().getUpdates()) { - // If this was a pipeline style update, record which stages were being used. + // If this was a pipeline style update, record that pipeline-style was used and + // which stages were being used. const auto& updateMod = update.getU(); if (updateMod.type() == write_ops::UpdateModification::Type::kPipeline) { auto request = AggregationRequest(_batchedRequest.getNS(), updateMod.getUpdatePipeline()); auto pipeline = LiteParsedPipeline(request); pipeline.tickGlobalStageCounters(); + _updateMetrics->incrementExecutedWithAggregationPipeline(); + } + + // If this command had arrayFilters option, record that it was used. + if (update.getArrayFilters()) { + _updateMetrics->incrementExecutedWithArrayFilters(); } } break; @@ -593,6 +605,9 @@ private: bool _bypass; const OpMsgRequest* _request; BatchedCommandRequest _batchedRequest; + + // Update related command execution metrics. + UpdateMetrics* const _updateMetrics; }; class ClusterInsertCmd final : public ClusterWriteCmd { @@ -630,7 +645,7 @@ private: class ClusterUpdateCmd final : public ClusterWriteCmd { public: - ClusterUpdateCmd() : ClusterWriteCmd("update") {} + ClusterUpdateCmd() : ClusterWriteCmd("update"), _updateMetrics{"update"} {} private: class Invocation final : public InvocationBase { @@ -651,7 +666,8 @@ private: "Cannot specify runtime constants option to a mongos", !parsedRequest.hasRuntimeConstants()); parsedRequest.setRuntimeConstants(Variables::generateRuntimeConstants(opCtx)); - return stdx::make_unique<Invocation>(this, request, std::move(parsedRequest)); + return stdx::make_unique<Invocation>( + this, request, std::move(parsedRequest), &_updateMetrics); } std::string help() const override { @@ -661,6 +677,9 @@ private: LogicalOp getLogicalOp() const override { return LogicalOp::opUpdate; } + + // Update related command execution metrics. + UpdateMetrics _updateMetrics; } clusterUpdateCmd; class ClusterDeleteCmd final : public ClusterWriteCmd { |