summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJess Balint <jbalint@gmail.com>2022-04-21 20:34:27 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-21 21:56:15 +0000
commita253db72b08fd159332ad121e644fcea651d10db (patch)
tree18afd5147999ef65a602905f52a585321ad672a6
parent85c39ef1591fdacdf221d168a0ae5a8b08d8b5ce (diff)
downloadmongo-a253db72b08fd159332ad121e644fcea651d10db.tar.gz
SERVER-65271 serverStatus should allow fine-grained metrics exclusion
(cherry picked from commit d964e2b912e26a96ee61cf4272102c8b4415fd2e)
-rw-r--r--jstests/noPassthrough/server_status_metrics_exclusion.js65
-rw-r--r--src/mongo/db/commands/server_status_command.cpp10
-rw-r--r--src/mongo/db/commands/server_status_internal.cpp52
-rw-r--r--src/mongo/db/commands/server_status_internal.h9
4 files changed, 127 insertions, 9 deletions
diff --git a/jstests/noPassthrough/server_status_metrics_exclusion.js b/jstests/noPassthrough/server_status_metrics_exclusion.js
new file mode 100644
index 00000000000..2127882c9a3
--- /dev/null
+++ b/jstests/noPassthrough/server_status_metrics_exclusion.js
@@ -0,0 +1,65 @@
+/**
+ * Tests that serverStatus metrics are filtered when requested.
+ */
+(function() {
+"use strict";
+const mongod = MongoRunner.runMongod();
+const dbName = jsTestName();
+const db = mongod.getDB(dbName);
+
+// Verify some assumptions about metrics structure for later tests.
+let serverStatusMetrics = db.serverStatus().metrics;
+
+assert(serverStatusMetrics.document.hasOwnProperty("deleted"));
+assert(serverStatusMetrics.document.hasOwnProperty("inserted"));
+assert(serverStatusMetrics.query.hasOwnProperty("planCacheTotalSizeEstimateBytes"));
+
+// Exclude the "document.deleted" field.
+serverStatusMetrics = db.serverStatus({metrics: {document: {deleted: false}}}).metrics;
+
+assert(!serverStatusMetrics.document.hasOwnProperty("deleted"));
+assert(serverStatusMetrics.document.hasOwnProperty("inserted"));
+
+// Exclude the "document.deleted" and "document.inserted" fields.
+serverStatusMetrics =
+ db.serverStatus({metrics: {document: {deleted: false, inserted: false}}}).metrics;
+
+assert(!serverStatusMetrics.document.hasOwnProperty("deleted"));
+assert(!serverStatusMetrics.document.hasOwnProperty("inserted"));
+
+// Exclude the "document.deleted" and "query.planCacheTotalSizeEstimateBytes" fields.
+serverStatusMetrics =
+ db.serverStatus({
+ metrics: {document: {deleted: false}, query: {planCacheTotalSizeEstimateBytes: false}}
+ }).metrics;
+
+assert(!serverStatusMetrics.document.hasOwnProperty("deleted"));
+assert(!serverStatusMetrics.query.hasOwnProperty("planCacheTotalSizeEstimateBytes"));
+
+// Include a "true" value for the "document.deleted" field. It should be included (ie, no-op).
+serverStatusMetrics = db.serverStatus({metrics: {document: {deleted: true}}}).metrics;
+
+assert(serverStatusMetrics.document.hasOwnProperty("deleted"));
+assert(serverStatusMetrics.document.hasOwnProperty("inserted"));
+
+// Attempt a non-boolean values which should be rejected (uassert).
+assert.commandFailedWithCode(db.serverStatus({metrics: {document: "Non-boolean"}}),
+ [ErrorCodes.TypeMismatch]);
+
+assert.commandFailedWithCode(db.serverStatus({metrics: {document: {deleted: "Non-boolean"}}}),
+ [ErrorCodes.TypeMismatch]);
+
+assert.commandFailedWithCode(db.serverStatus({metrics: {document: {deleted: ["Non-boolean"]}}}),
+ [ErrorCodes.TypeMismatch]);
+
+assert.commandFailedWithCode(
+ db.serverStatus({metrics: {document: {deleted: {invalidObjectAtLeftLevel: 1}}}}),
+ [ErrorCodes.TypeMismatch]);
+
+// Exclude the "document" subtree.
+serverStatusMetrics = db.serverStatus({metrics: {document: false}}).metrics;
+
+assert(!serverStatusMetrics.hasOwnProperty("document"));
+
+MongoRunner.stopMongod(mongod);
+})();
diff --git a/src/mongo/db/commands/server_status_command.cpp b/src/mongo/db/commands/server_status_command.cpp
index c5691a7b4ad..482ad472fb2 100644
--- a/src/mongo/db/commands/server_status_command.cpp
+++ b/src/mongo/db/commands/server_status_command.cpp
@@ -128,11 +128,17 @@ public:
// --- counters
bool includeMetricTree = MetricTree::theMetricTree != nullptr;
- if (cmdObj["metrics"].type() && !cmdObj["metrics"].trueValue())
+ auto metricsEl = cmdObj["metrics"_sd];
+ if (metricsEl.type() && !metricsEl.trueValue())
includeMetricTree = false;
if (includeMetricTree) {
- MetricTree::theMetricTree->appendTo(result);
+ if (metricsEl.type() == BSONType::Object) {
+ MetricTree::theMetricTree->appendTo(BSON("metrics" << metricsEl.embeddedObject()),
+ result);
+ } else {
+ MetricTree::theMetricTree->appendTo(result);
+ }
}
// --- some hard coded global things hard to pull out
diff --git a/src/mongo/db/commands/server_status_internal.cpp b/src/mongo/db/commands/server_status_internal.cpp
index c981a65fd4b..82698b5e59a 100644
--- a/src/mongo/db/commands/server_status_internal.cpp
+++ b/src/mongo/db/commands/server_status_internal.cpp
@@ -31,6 +31,7 @@
#include <iostream>
+#include "mongo/bson/bsontypes.h"
#include "mongo/db/commands/server_status_metric.h"
#include "mongo/util/str.h"
@@ -71,16 +72,53 @@ void MetricTree::_add(const string& path, ServerStatusMetric* metric) {
}
void MetricTree::appendTo(BSONObjBuilder& b) const {
- for (map<string, ServerStatusMetric*>::const_iterator i = _metrics.begin(); i != _metrics.end();
- ++i) {
- i->second->appendAtLeaf(b);
+ for (const auto& i : _metrics) {
+ i.second->appendAtLeaf(b);
}
- for (map<string, MetricTree*>::const_iterator i = _subtrees.begin(); i != _subtrees.end();
- ++i) {
- BSONObjBuilder bb(b.subobjStart(i->first));
- i->second->appendTo(bb);
+ for (const auto& i : _subtrees) {
+ BSONObjBuilder bb(b.subobjStart(i.first));
+ i.second->appendTo(bb);
bb.done();
}
}
+
+void MetricTree::appendTo(const BSONObj& excludePaths, BSONObjBuilder& b) const {
+ auto fieldNamesInExclude = excludePaths.getFieldNames<stdx::unordered_set<std::string>>();
+ for (const auto& i : _metrics) {
+ auto key = i.first;
+ auto el = fieldNamesInExclude.contains(key) ? excludePaths.getField(key) : BSONElement();
+ if (el) {
+ uassert(ErrorCodes::TypeMismatch,
+ "Exclusion value for a leaf must be a boolean.",
+ el.type() == Bool);
+ if (el.boolean() == false) {
+ continue;
+ }
+ }
+ i.second->appendAtLeaf(b);
+ }
+
+ for (const auto& i : _subtrees) {
+ auto key = i.first;
+ auto el = fieldNamesInExclude.contains(key) ? excludePaths.getField(key) : BSONElement();
+ if (el) {
+ uassert(ErrorCodes::TypeMismatch,
+ "Exclusion value must be a boolean or a nested object.",
+ el.type() == Bool || el.type() == Object);
+ if (el.isBoolean() && el.boolean() == false) {
+ continue;
+ }
+ }
+
+ BSONObjBuilder bb(b.subobjStart(key));
+ if (el.type() == Object) {
+ i.second->appendTo(el.embeddedObject(), bb);
+ } else {
+ i.second->appendTo(bb);
+ }
+ bb.done();
+ }
+}
+
} // namespace mongo
diff --git a/src/mongo/db/commands/server_status_internal.h b/src/mongo/db/commands/server_status_internal.h
index f9bde775db3..b59b0b36762 100644
--- a/src/mongo/db/commands/server_status_internal.h
+++ b/src/mongo/db/commands/server_status_internal.h
@@ -42,8 +42,17 @@ class MetricTree {
public:
void add(ServerStatusMetric* metric);
+ /**
+ * Append the metrics tree to the given BSON builder.
+ */
void appendTo(BSONObjBuilder& b) const;
+ /**
+ * Implementation of appendTo which allows tree of exclude paths. The alternative overload is
+ * preferred to avoid overhead when no excludes are present.
+ */
+ void appendTo(const BSONObj& excludePaths, BSONObjBuilder& b) const;
+
static MetricTree* theMetricTree;
private: