summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMindaugas Malinauskas <mindaugas.malinauskas@mongodb.com>2020-07-14 14:25:40 +0300
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-10-13 12:21:50 +0000
commit392e8c8ffbe242805bf5a82437d0ae39d1fc6a8a (patch)
treef39d309449b2296a7e9c41a4e40c83a73a2bad04
parent5454f3bf6391624e42efbc2538536cb0e8bdaab2 (diff)
downloadmongo-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)
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_replication_100ms_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_replication_10sec_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_replication_1sec_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_replication_default_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_standalone_100ms_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_standalone_10sec_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_standalone_1sec_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/logical_session_cache_standalone_default_refresh_jscore_passthrough.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/retryable_writes_jscore_passthrough.yml4
-rw-r--r--jstests/core/find_and_modify_metrics.js78
-rw-r--r--jstests/core/update_metrics.js79
-rw-r--r--jstests/libs/parallelTester.js4
-rw-r--r--src/mongo/db/commands/SConscript2
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp10
-rw-r--r--src/mongo/db/commands/update_metrics.cpp60
-rw-r--r--src/mongo/db/commands/update_metrics.h82
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp31
-rw-r--r--src/mongo/s/commands/cluster_find_and_modify_cmd.cpp10
-rw-r--r--src/mongo/s/commands/cluster_write_cmd.cpp29
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 {