From 08bcb09f03328105f0e21327f1f758bec3123450 Mon Sep 17 00:00:00 2001 From: Jess Balint Date: Thu, 21 Apr 2022 15:08:20 +0000 Subject: SERVER-65271 serverStatus should allow fine-grained metrics exclusion (cherry picked from commit d964e2b912e26a96ee61cf4272102c8b4415fd2e) --- .../server_status_metrics_exclusion.js | 66 ++++++++++++++++++++++ src/mongo/db/commands/server_status_command.cpp | 10 +++- src/mongo/db/commands/server_status_internal.cpp | 52 ++++++++++++++--- src/mongo/db/commands/server_status_internal.h | 9 +++ 4 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 jstests/noPassthrough/server_status_metrics_exclusion.js diff --git a/jstests/noPassthrough/server_status_metrics_exclusion.js b/jstests/noPassthrough/server_status_metrics_exclusion.js new file mode 100644 index 00000000000..d088231607d --- /dev/null +++ b/jstests/noPassthrough/server_status_metrics_exclusion.js @@ -0,0 +1,66 @@ +/** + * 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.dotsAndDollarsFields.hasOwnProperty("inserts")); +assert(serverStatusMetrics.dotsAndDollarsFields.hasOwnProperty("updates")); + +// 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 "dotsAndDollarsFields.inserts" fields. +serverStatusMetrics = + db.serverStatus({ + metrics: {document: {deleted: false}, dotsAndDollarsFields: {inserts: false}} + }).metrics; + +assert(!serverStatusMetrics.document.hasOwnProperty("deleted")); +assert(!serverStatusMetrics.dotsAndDollarsFields.hasOwnProperty("inserts")); + +// 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.InvalidBSONType]); + +assert.commandFailedWithCode(db.serverStatus({metrics: {document: {deleted: "Non-boolean"}}}), + [ErrorCodes.InvalidBSONType]); + +assert.commandFailedWithCode(db.serverStatus({metrics: {document: {deleted: ["Non-boolean"]}}}), + [ErrorCodes.InvalidBSONType]); + +assert.commandFailedWithCode( + db.serverStatus({metrics: {document: {deleted: {invalidObjectAtLeftLevel: 1}}}}), + [ErrorCodes.InvalidBSONType]); + +// 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 6435da21c95..41d5a45a0f9 100644 --- a/src/mongo/db/commands/server_status_command.cpp +++ b/src/mongo/db/commands/server_status_command.cpp @@ -133,11 +133,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..e4ea0e879c3 100644 --- a/src/mongo/db/commands/server_status_internal.cpp +++ b/src/mongo/db/commands/server_status_internal.cpp @@ -31,6 +31,7 @@ #include +#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::const_iterator i = _metrics.begin(); i != _metrics.end(); - ++i) { - i->second->appendAtLeaf(b); + for (const auto& i : _metrics) { + i.second->appendAtLeaf(b); } - for (map::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>(); + for (const auto& i : _metrics) { + auto key = i.first; + auto el = fieldNamesInExclude.contains(key) ? excludePaths.getField(key) : BSONElement(); + if (el) { + uassert(ErrorCodes::InvalidBSONType, + "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::InvalidBSONType, + "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: -- cgit v1.2.1