summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2019-01-16 13:48:10 -0500
committerTess Avitabile <tess.avitabile@mongodb.com>2019-02-01 09:26:37 -0500
commit034e25ab50efabb62038f802d750386f6ce20230 (patch)
tree3000da85cc3dcd02190cccdd5c33816fc2528947
parent49cbe21f9a7cd0175aa6db3cd82035c44c7b97cd (diff)
downloadmongo-034e25ab50efabb62038f802d750386f6ce20230.tar.gz
SERVER-38998 Create serverStatus metrics for readConcern
(cherry picked from commit 5e4dd450fa2e3d2900cc4ac204385ab613c36cc5)
-rw-r--r--jstests/noPassthrough/server_read_concern_metrics.js227
-rw-r--r--src/mongo/db/catalog/SConscript3
-rw-r--r--src/mongo/db/commands/find_cmd.cpp2
-rw-r--r--src/mongo/db/read_concern_stats.idl60
-rw-r--r--src/mongo/db/server_read_concern_metrics.cpp111
-rw-r--r--src/mongo/db/server_read_concern_metrics.h70
-rw-r--r--src/mongo/db/service_entry_point_mongod.cpp2
7 files changed, 475 insertions, 0 deletions
diff --git a/jstests/noPassthrough/server_read_concern_metrics.js b/jstests/noPassthrough/server_read_concern_metrics.js
new file mode 100644
index 00000000000..ba1dbd7dd80
--- /dev/null
+++ b/jstests/noPassthrough/server_read_concern_metrics.js
@@ -0,0 +1,227 @@
+// Tests readConcern level metrics in the serverStatus output.
+// @tags: [requires_majority_read_concern, requires_wiredtiger]
+(function() {
+ "use strict";
+
+ // Verifies that the server status response has the fields that we expect.
+ function verifyServerStatusFields(serverStatusResponse) {
+ assert(serverStatusResponse.hasOwnProperty("opReadConcernCounters"),
+ "Expected the serverStatus response to have a 'opReadConcernCounters' field\n" +
+ tojson(serverStatusResponse));
+ assert(
+ serverStatusResponse.opReadConcernCounters.hasOwnProperty("available"),
+ "The 'opReadConcernCounters' field in serverStatus did not have the 'available' field\n" +
+ tojson(serverStatusResponse.opReadConcernCounters));
+ assert(
+ serverStatusResponse.opReadConcernCounters.hasOwnProperty("linearizable"),
+ "The 'opReadConcernCounters' field in serverStatus did not have the 'linearizable' field\n" +
+ tojson(serverStatusResponse.opReadConcernCounters));
+ assert(
+ serverStatusResponse.opReadConcernCounters.hasOwnProperty("local"),
+ "The 'opReadConcernCounters' field in serverStatus did not have the 'local' field\n" +
+ tojson(serverStatusResponse.opReadConcernCounters));
+ assert(
+ serverStatusResponse.opReadConcernCounters.hasOwnProperty("majority"),
+ "The 'opReadConcernCounters' field in serverStatus did not have the 'majority' field\n" +
+ tojson(serverStatusResponse.opReadConcernCounters));
+ assert(serverStatusResponse.opReadConcernCounters.hasOwnProperty("none"),
+ "The 'opReadConcernCounters' field in serverStatus did not have the 'none' field\n" +
+ tojson(serverStatusResponse.opReadConcernCounters));
+ }
+
+ // Verifies that the given value of the server status response is incremented in the way
+ // we expect.
+ function verifyServerStatusChange(initialStats, newStats, valueName, expectedIncrement) {
+ assert.eq(initialStats[valueName] + expectedIncrement,
+ newStats[valueName],
+ "expected " + valueName + " to increase by " + expectedIncrement +
+ ", initialStats: " + tojson(initialStats) + ", newStats: " +
+ tojson(newStats));
+ }
+
+ const rst = new ReplSetTest({nodes: 1});
+ rst.startSet();
+ rst.initiate();
+ const primary = rst.getPrimary();
+ const dbName = "test";
+ const collName = "server_read_concern_metrics";
+ const testDB = primary.getDB(dbName);
+ const testColl = testDB[collName];
+ testDB.runCommand({drop: collName, writeConcern: {w: "majority"}});
+ assert.writeOK(testColl.insert({_id: 0}));
+
+ // Get initial serverStatus.
+ let serverStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(serverStatus);
+
+ // Run a find with no readConcern.
+ assert.eq(testColl.find().itcount(), 1);
+ let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 1);
+ serverStatus = newStatus;
+
+ // Run a find with a readConcern with no level.
+ assert.commandWorked(
+ testDB.runCommand({find: collName, readConcern: {afterClusterTime: Timestamp(1, 1)}}));
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 1);
+ serverStatus = newStatus;
+
+ // Run a legacy query.
+ primary.forceReadMode("legacy");
+ assert.eq(testColl.find().itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 1);
+ primary.forceReadMode("commands");
+ serverStatus = newStatus;
+
+ // Run a find with a readConcern level available.
+ assert.eq(testColl.find().readConcern("available").itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 1);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // Run a find with a readConcern level linearizable.
+ assert.eq(testColl.find().readConcern("linearizable").itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 1);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // Run a find with a readConcern level local.
+ assert.eq(testColl.find().readConcern("local").itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 1);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // Run a find with a readConcern level majority.
+ assert.eq(testColl.find().readConcern("majority").itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 1);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // Aggregation does not count toward readConcern metrics. Aggregation is counted as a 'command'
+ // in the 'opCounters' serverStatus section, and we only track the readConcern of queries
+ // tracked in 'opCounters.query'.
+ assert.eq(testColl.aggregate([]).itcount(), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // The count command does not count toward readConcern metrics. The count command is counted as
+ // a 'command' in the 'opCounters' serverStatus section, and we only track the readConcern of
+ // queries tracked in 'opCounters.query'.
+ assert.eq(testColl.count({_id: 0}), 1);
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+ serverStatus = newStatus;
+
+ // getMore does not count toward readConcern metrics. getMore inherits the readConcern of the
+ // originating command. It is not counted in 'opCounters.query'.
+ let res = assert.commandWorked(testDB.runCommand({find: collName, batchSize: 0}));
+ serverStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ assert.commandWorked(testDB.runCommand({getMore: res.cursor.id, collection: collName}));
+ newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
+ verifyServerStatusFields(newStatus);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "available", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "linearizable", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "local", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "majority", 0);
+ verifyServerStatusChange(
+ serverStatus.opReadConcernCounters, newStatus.opReadConcernCounters, "none", 0);
+
+ rst.stopSet();
+}());
diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript
index 864895432d3..69c2704f139 100644
--- a/src/mongo/db/catalog/SConscript
+++ b/src/mongo/db/catalog/SConscript
@@ -134,9 +134,12 @@ env.Library(
env.Library(
target='catalog_raii',
source=[
+ '$BUILD_DIR/mongo/db/server_read_concern_metrics.cpp',
'catalog_raii.cpp',
+ env.Idlc('$BUILD_DIR/mongo/db/read_concern_stats.idl')[0],
],
LIBDEPS=[
+ '$BUILD_DIR/mongo/idl/idl_parser',
'$BUILD_DIR/mongo/db/catalog/database_holder',
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
'$BUILD_DIR/mongo/db/views/views',
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index c913dde609e..c82234081d3 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -50,6 +50,7 @@
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/s/collection_sharding_state.h"
#include "mongo/db/server_parameters.h"
+#include "mongo/db/server_read_concern_metrics.h"
#include "mongo/db/service_context.h"
#include "mongo/db/stats/counters.h"
#include "mongo/rpc/get_status_from_command_result.h"
@@ -228,6 +229,7 @@ public:
BSONObjBuilder& result) override {
// Although it is a command, a find command gets counted as a query.
globalOpCounters.gotQuery();
+ ServerReadConcernMetrics::get(opCtx)->recordReadConcern(repl::ReadConcernArgs::get(opCtx));
// Parse the command BSON to a QueryRequest.
const bool isExplain = false;
diff --git a/src/mongo/db/read_concern_stats.idl b/src/mongo/db/read_concern_stats.idl
new file mode 100644
index 00000000000..c17930ce893
--- /dev/null
+++ b/src/mongo/db/read_concern_stats.idl
@@ -0,0 +1,60 @@
+# Copyright (C) 2018-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.
+#
+
+# This IDL file describes the BSON format for ReadConcernStats, and
+# handles the serialization to and deserialization from its BSON representation
+# for that class.
+
+global:
+ cpp_namespace: "mongo"
+
+imports:
+ - "mongo/idl/basic_types.idl"
+
+structs:
+
+ ReadConcernStats:
+ description: "A struct representing the section of the server status
+ command with information about readConcern levels used by operations"
+ strict: true
+ fields:
+ available:
+ type: long
+ default: 0
+ linearizable:
+ type: long
+ default: 0
+ local:
+ type: long
+ default: 0
+ majority:
+ type: long
+ default: 0
+ none:
+ type: long
+ default: 0
diff --git a/src/mongo/db/server_read_concern_metrics.cpp b/src/mongo/db/server_read_concern_metrics.cpp
new file mode 100644
index 00000000000..c4e41a49ef1
--- /dev/null
+++ b/src/mongo/db/server_read_concern_metrics.cpp
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2018-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/server_read_concern_metrics.h"
+
+#include "mongo/db/commands/server_status.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/read_concern_stats_gen.h"
+#include "mongo/db/service_context.h"
+
+namespace mongo {
+namespace {
+const auto ServerReadConcernMetricsDecoration =
+ ServiceContext::declareDecoration<ServerReadConcernMetrics>();
+} // namespace
+
+ServerReadConcernMetrics* ServerReadConcernMetrics::get(ServiceContext* service) {
+ return &ServerReadConcernMetricsDecoration(service);
+}
+
+ServerReadConcernMetrics* ServerReadConcernMetrics::get(OperationContext* opCtx) {
+ return get(opCtx->getServiceContext());
+}
+
+void ServerReadConcernMetrics::recordReadConcern(const repl::ReadConcernArgs& readConcernArgs) {
+ if (!readConcernArgs.hasLevel()) {
+ _noLevelCount.fetchAndAdd(1);
+ return;
+ }
+
+ switch (readConcernArgs.getLevel()) {
+ case repl::ReadConcernLevel::kAvailableReadConcern:
+ _levelAvailableCount.fetchAndAdd(1);
+ break;
+
+ case repl::ReadConcernLevel::kLinearizableReadConcern:
+ _levelLinearizableCount.fetchAndAdd(1);
+ break;
+
+ case repl::ReadConcernLevel::kLocalReadConcern:
+ _levelLocalCount.fetchAndAdd(1);
+ break;
+
+ case repl::ReadConcernLevel::kMajorityReadConcern:
+ _levelMajorityCount.fetchAndAdd(1);
+ break;
+
+ default:
+ MONGO_UNREACHABLE;
+ }
+}
+
+void ServerReadConcernMetrics::updateStats(ReadConcernStats* stats, OperationContext* opCtx) {
+ stats->setAvailable(_levelAvailableCount.load());
+ stats->setLinearizable(_levelLinearizableCount.load());
+ stats->setLocal(_levelLocalCount.load());
+ stats->setMajority(_levelMajorityCount.load());
+ stats->setNone(_noLevelCount.load());
+}
+
+namespace {
+class OpReadConcernCountersSSS : public ServerStatusSection {
+public:
+ OpReadConcernCountersSSS() : ServerStatusSection("opReadConcernCounters") {}
+
+ ~OpReadConcernCountersSSS() override = default;
+
+ bool includeByDefault() const override {
+ return true;
+ }
+
+ BSONObj generateSection(OperationContext* opCtx,
+ const BSONElement& configElement) const override {
+ ReadConcernStats stats;
+ ServerReadConcernMetrics::get(opCtx)->updateStats(&stats, opCtx);
+ return stats.toBSON();
+ }
+
+} opReadConcernCountersSSS;
+} // namespace
+
+} // namespace mongo
diff --git a/src/mongo/db/server_read_concern_metrics.h b/src/mongo/db/server_read_concern_metrics.h
new file mode 100644
index 00000000000..07be28f4a5b
--- /dev/null
+++ b/src/mongo/db/server_read_concern_metrics.h
@@ -0,0 +1,70 @@
+
+/**
+ * Copyright (C) 2018-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/db/operation_context.h"
+#include "mongo/db/read_concern_stats_gen.h"
+#include "mongo/db/repl/read_concern_args.h"
+#include "mongo/db/service_context.h"
+
+namespace mongo {
+
+/**
+ * Container for server-wide statistics on readConcern levels used by operations.
+ */
+class ServerReadConcernMetrics {
+ MONGO_DISALLOW_COPYING(ServerReadConcernMetrics);
+
+public:
+ ServerReadConcernMetrics() = default;
+
+ static ServerReadConcernMetrics* get(ServiceContext* service);
+ static ServerReadConcernMetrics* get(OperationContext* opCtx);
+
+ /**
+ * Updates counter for the level of 'readConcernArgs'.
+ */
+ void recordReadConcern(const repl::ReadConcernArgs& readConcernArgs);
+
+ /**
+ * Appends the accumulated stats to a readConcern stats object.
+ */
+ void updateStats(ReadConcernStats* stats, OperationContext* opCtx);
+
+private:
+ AtomicWord<unsigned long long> _levelAvailableCount{0};
+ AtomicWord<unsigned long long> _levelLinearizableCount{0};
+ AtomicWord<unsigned long long> _levelLocalCount{0};
+ AtomicWord<unsigned long long> _levelMajorityCount{0};
+ AtomicWord<unsigned long long> _noLevelCount{0};
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp
index bdd0dab18af..04a3e5055e8 100644
--- a/src/mongo/db/service_entry_point_mongod.cpp
+++ b/src/mongo/db/service_entry_point_mongod.cpp
@@ -66,6 +66,7 @@
#include "mongo/db/s/sharded_connection_info.h"
#include "mongo/db/s/sharding_state.h"
#include "mongo/db/server_options.h"
+#include "mongo/db/server_read_concern_metrics.h"
#include "mongo/db/session_catalog.h"
#include "mongo/db/stats/counters.h"
#include "mongo/db/stats/top.h"
@@ -911,6 +912,7 @@ DbResponse receivedQuery(OperationContext* opCtx,
const Message& m) {
invariant(!nss.isCommand());
globalOpCounters.gotQuery();
+ ServerReadConcernMetrics::get(opCtx)->recordReadConcern(repl::ReadConcernArgs::get(opCtx));
DbMessage d(m);
QueryMessage q(d);