summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilena Ivanova <milena.ivanova@mongodb.com>2021-11-23 14:47:17 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-11-23 15:23:15 +0000
commitcaa1e34fe651d74808b9ebe2785c660fd3da20d9 (patch)
tree4ee2b1db511a745a252539b89d1d52faa467de31
parent2fc19024b2c270e027469bfe499bf74817d7d91b (diff)
downloadmongo-caa1e34fe651d74808b9ebe2785c660fd3da20d9.tar.gz
SERVER-56602 Track usage of match expressions in serverStatus
(cherry picked from commit 98b4a0a2b2c4a230efe1c2ddc3da487a575d670d) (cherry picked from commit e7a9d8465911803552939ab364ef2cada688be5b)
-rw-r--r--jstests/noPassthrough/operator_counters_match.js285
-rw-r--r--src/mongo/db/commands/find_cmd.cpp20
-rw-r--r--src/mongo/db/commands/run_aggregate.cpp2
-rw-r--r--src/mongo/db/matcher/SConscript1
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp28
-rw-r--r--src/mongo/db/matcher/expression_leaf.h2
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp49
-rw-r--r--src/mongo/db/pipeline/SConscript1
-rw-r--r--src/mongo/db/pipeline/expression_context.cpp27
-rw-r--r--src/mongo/db/pipeline/expression_context.h35
-rw-r--r--src/mongo/db/query/canonical_query.cpp5
-rw-r--r--src/mongo/db/stats/SConscript1
-rw-r--r--src/mongo/db/stats/counters.cpp1
-rw-r--r--src/mongo/db/stats/counters.h33
14 files changed, 469 insertions, 21 deletions
diff --git a/jstests/noPassthrough/operator_counters_match.js b/jstests/noPassthrough/operator_counters_match.js
new file mode 100644
index 00000000000..a23f9ef75ce
--- /dev/null
+++ b/jstests/noPassthrough/operator_counters_match.js
@@ -0,0 +1,285 @@
+/**
+ * Tests counters for match expressions.
+ * @tags: [requires_fcv_42]
+ */
+
+(function() {
+"use strict";
+const mongod = MongoRunner.runMongod();
+const db = mongod.getDB(jsTest.name());
+const collName = jsTest.name();
+const coll = db[collName];
+coll.drop();
+
+assert.commandWorked(coll.insert({_id: 0, a: 0, b: "foo", c: [10, 50]}));
+assert.commandWorked(coll.insert({_id: 1, a: 1, b: "foo", c: [10, 20, 30]}));
+assert.commandWorked(coll.insert({_id: 2, a: 2, b: "bar"}));
+assert.commandWorked(coll.insert({_id: 3, a: 3, c: [20]}));
+
+/**
+ * Run the command and check that the specified match expression counters are increased by the given
+ * value.
+ * - countersToIncrease: document with elements <match expression> : < increment value>.
+ */
+function checkCountersWithValues(command, countersToIncrease) {
+ let beforeMetrics = {};
+ const metrics = db.serverStatus().metrics.operatorCounters.match;
+ for (let [exprName, inc] of Object.entries(countersToIncrease)) {
+ beforeMetrics[exprName] = metrics[exprName];
+ }
+
+ command();
+
+ const afterMetrics = db.serverStatus().metrics.operatorCounters.match;
+ for (let [exprName, inc] of Object.entries(countersToIncrease)) {
+ assert.eq(afterMetrics[exprName], beforeMetrics[exprName] + inc, exprName);
+ }
+}
+
+/**
+ * Simplified version of the above function where the counters are expected to increase by 1.
+ * - countersToIncrease: array of match expression names or a single expression name.
+ */
+function checkCounters(command, countersToIncrease) {
+ if (!Array.isArray(countersToIncrease)) {
+ countersToIncrease = [countersToIncrease];
+ }
+ checkCountersWithValues(command, countersToIncrease.reduce((acc, val) => {
+ acc[val] = 1;
+ return acc;
+ }, {}));
+}
+
+/**
+ * Run a command expected to fail and check that the specified match expression counters are not
+ * increased.
+ * - countersNotToIncrease: array of match expression names.
+ */
+function checkCountersWithError(command, errorCode, countersNotToIncrease) {
+ let beforeMetrics = {};
+ const metrics = db.serverStatus().metrics.operatorCounters.match;
+ countersNotToIncrease.forEach((exprName) => {
+ beforeMetrics[exprName] = metrics[exprName];
+ });
+
+ const error = assert.throws(command);
+ assert.commandFailedWithCode(error, errorCode);
+
+ const afterMetrics = db.serverStatus().metrics.operatorCounters.match;
+ countersNotToIncrease.forEach((exprName) => {
+ assert.eq(afterMetrics[exprName], beforeMetrics[exprName], exprName);
+ });
+}
+
+// Match expression counters in find.
+checkCounters(() => assert.eq(1, coll.find({a: 1}).itcount()), "$eq");
+
+checkCounters(() => assert.eq(2, coll.find({b: {$eq: "foo"}}).itcount()), "$eq");
+
+checkCounters(() => assert.eq(2, coll.find({a: {$gt: 1}}).itcount()), "$gt");
+
+checkCounters(() => assert.eq(3, coll.find({a: {$gte: 1}}).itcount()), "$gte");
+
+checkCounters(() => assert.eq(1, coll.find({a: {$lt: 1}}).itcount()), "$lt");
+
+checkCounters(() => assert.eq(2, coll.find({a: {$lte: 1}}).itcount()), "$lte");
+
+checkCounters(() => assert.eq(3, coll.find({a: {$ne: 1}}).itcount()), "$ne");
+
+checkCounters(() => assert.eq(2, coll.find({a: {$in: [1, 2]}}).itcount()), "$in");
+
+checkCounters(() => assert.eq(2, coll.find({a: {$nin: [0, 2]}}).itcount()), "$nin");
+
+checkCounters(() => assert.eq(1, coll.find({a: {$gt: 1, $in: [1, 2]}}).itcount()), ["$in", "$gt"]);
+
+checkCounters(() => assert.eq(3, coll.find({c: {$exists: true}}).itcount()), "$exists");
+
+checkCounters(() => assert.eq(4, coll.find({a: {$type: 1}}).itcount()), "$type");
+
+checkCounters(() => assert.eq(2, coll.find({a: {$mod: [2, 0]}}).itcount()), "$mod");
+
+checkCounters(() => assert.eq(2, coll.find({b: {$regex: /^f/}}).itcount()), "$regex");
+
+checkCounters(() => assert.eq(2, coll.find({b: /^f/}).itcount()), "$regex");
+
+checkCounters(() => assert.eq(2, coll.find({b: {$not: /^f/}}).itcount()), ["$regex", "$not"]);
+
+checkCounters(() => assert.eq(2, coll.find({$jsonSchema: {required: ["a", "b", "c"]}}).itcount()),
+ "$jsonSchema");
+
+checkCounters(() => assert.eq(4, coll.find({$alwaysTrue: 1}).itcount()), "$alwaysTrue");
+
+checkCounters(() => assert.eq(0, coll.find({$alwaysFalse: 1}).itcount()), "$alwaysFalse");
+
+// Increments only the $expr counter, counters for $lt, and $add are recorded in
+// operatorCounters.expressions metrics.
+checkCounters(() => assert.eq(4, coll.find({$expr: {$lt: ["$a", {$add: [1, 10]}]}}).itcount()),
+ "$expr");
+
+checkCounters(
+ () => assert.eq(2, coll.find({a: {$mod: [2, 0]}, $comment: "Find even values."}).itcount()),
+ ["$mod", "$comment"]);
+
+checkCounters(() => assert.eq(1,
+ coll.find({
+ $where: function() {
+ return (this.a == 1);
+ }
+ })
+ .itcount()),
+ "$where");
+
+// Array query operators.
+checkCounters(() => assert.eq(2, coll.find({c: {$elemMatch: {$gt: 10, $lt: 50}}}).itcount()),
+ ["$elemMatch", "$gt", "$lt"]);
+
+checkCounters(() => assert.eq(1, coll.find({c: {$size: 2}}).itcount()), "$size");
+
+checkCounters(() => assert.eq(1, coll.find({c: {$all: [10, 20]}}).itcount()), "$all");
+
+// Logical operators.
+checkCountersWithValues(() => assert.eq(1, coll.find({$and: [{c: 10}, {c: 20}]}).itcount()),
+ {"$and": 1, "$eq": 2});
+
+checkCounters(() => assert.eq(2, coll.find({$and: [{c: 10}, {a: {$lt: 2}}]}).itcount()),
+ ["$and", "$lt", "$eq"]);
+
+checkCountersWithValues(() => assert.eq(2, coll.find({$or: [{c: 50}, {a: 2}]}).itcount()),
+ {"$or": 1, "$eq": 2});
+
+checkCounters(() => assert.eq(1, coll.find({$nor: [{a: {$gt: 1}}, {c: 50}]}).itcount()),
+ ["$nor", "$eq", "$gt"]);
+
+checkCounters(() => assert.eq(2, coll.find({b: {$not: {$eq: "foo"}}}).itcount()), ["$not", "$eq"]);
+
+// Bitwise query operators.
+checkCounters(() => assert.eq(2, coll.find({a: {$bitsAllClear: [0]}}).itcount()), "$bitsAllClear");
+
+checkCounters(() => assert.eq(1, coll.find({a: {$bitsAllSet: [0, 1]}}).itcount()), "$bitsAllSet");
+
+checkCounters(() => assert.eq(3, coll.find({a: {$bitsAnyClear: [0, 1]}}).itcount()),
+ "$bitsAnyClear");
+
+checkCounters(() => assert.eq(3, coll.find({a: {$bitsAnySet: [0, 1]}}).itcount()), "$bitsAnySet");
+
+// Invalid expressions do not increment any counter.
+checkCountersWithError(() => coll.find({$or: [{c: {$size: 'a'}}, {a: 2}]}).itcount(),
+ ErrorCodes.BadValue,
+ ["$or", "$eq", "$size"]);
+
+// Match expression counters in aggregation pipelines.
+
+let pipeline = [{$match: {_id: 1}}];
+checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$eq");
+
+pipeline = [{$match: {_id: {$gt: 0}}}];
+checkCounters(() => assert.eq(3, coll.aggregate(pipeline).itcount()), "$gt");
+
+pipeline = [{$match: {$and: [{c: 10}, {a: {$lt: 2}}]}}];
+checkCounters(() => assert.eq(2, coll.aggregate(pipeline).itcount()), ["$and", "$eq", "$lt"]);
+
+pipeline = [{$match: {c: {$type: 4}}}];
+checkCounters(() => assert.eq(3, coll.aggregate(pipeline).itcount()), "$type");
+
+pipeline = [{$match: {c: {$exists: false}}}];
+checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$exists");
+
+pipeline = [{$match: {c: {$size: 1}}}];
+checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$size");
+
+pipeline = [{$match: {a: {$bitsAllSet: [0, 1]}}}];
+checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$bitsAllSet");
+
+// Invalid expressions do not increment any counter.
+checkCountersWithError(
+ () => coll.aggregate([{$match: {$and: [{b: /^fo/}, {a: {$mod: ['z', 2]}}]}}]).itcount(),
+ ErrorCodes.BadValue,
+ ["$and", "$regex", "$mod"]);
+
+// Text search.
+const textCollName = "myTextCollection";
+const textColl = db[textCollName];
+
+textColl.drop();
+assert.commandWorked(textColl.insert({_id: 0, title: "coffee"}));
+assert.commandWorked(textColl.insert({_id: 1, title: "Bake a cake"}));
+assert.commandWorked(textColl.insert({_id: 2, title: "Cake with coffee"}));
+
+assert.commandWorked(textColl.createIndex({title: "text"}));
+
+checkCounters(
+ () => assert.eq(1, textColl.find({$text: {$search: "cake", $caseSensitive: true}}).itcount()),
+ "$text");
+
+// Geospatial expressions.
+const geoCollName = "myGeoCollection";
+const geoColl = db[geoCollName];
+
+geoColl.drop();
+assert.commandWorked(geoColl.insert({_id: 0, location: {type: "Point", coordinates: [10, 20]}}));
+assert.commandWorked(geoColl.createIndex({location: "2dsphere"}));
+
+checkCounters(() => assert.eq(1,
+ geoColl
+ .find({
+ location: {
+ $near: {
+ $geometry: {type: "Point", coordinates: [10, 20]},
+ $minDistance: 0,
+ $maxDistance: 100
+ }
+ }
+ })
+ .itcount()),
+ "$near");
+
+checkCounters(() => assert.eq(1,
+ geoColl
+ .find({
+ location: {
+ $nearSphere: {
+ $geometry: {type: "Point", coordinates: [10.001, 20]},
+ $minDistance: 100,
+ $maxDistance: 500
+ }
+ }
+ })
+ .itcount()),
+ "$nearSphere");
+
+checkCounters(
+ () => assert.eq(
+ 1,
+ geoColl
+ .find({
+ location: {
+ $geoWithin: {
+ $geometry: {
+ type: "Polygon",
+ coordinates: [[[12, 18], [12, 21], [8, 21], [8, 18], [12, 18]]]
+ },
+ }
+ }
+ })
+ .itcount()),
+ "$geoWithin");
+
+checkCounters(
+ () => assert.eq(
+ 1,
+ geoColl
+ .find({
+ location: {
+ $geoIntersects: {
+ $geometry: {
+ type: "Polygon",
+ coordinates: [[[12, 18], [12, 21], [8, 21], [8, 18], [12, 18]]]
+ },
+ }
+ }
+ })
+ .itcount()),
+ "$geoIntersects");
+
+MongoRunner.stopMongod(mongod);
+})();
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index e7abdd8a240..e7313d834f9 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/pipeline/variables.h"
+#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/query/cursor_response.h"
#include "mongo/db/query/explain.h"
#include "mongo/db/query/find.h"
@@ -75,6 +76,21 @@ std::unique_ptr<QueryRequest> parseCmdObjectToQueryRequest(OperationContext* opC
return qr;
}
+boost::intrusive_ptr<ExpressionContext> makeExpressionContext(OperationContext* opCtx,
+ const QueryRequest& queryRequest) {
+ std::unique_ptr<CollatorInterface> collator;
+ if (!queryRequest.getCollation().isEmpty()) {
+ collator = uassertStatusOK(CollatorFactoryInterface::get(opCtx->getServiceContext())
+ ->makeFromBSON(queryRequest.getCollation()));
+ }
+
+ boost::intrusive_ptr<ExpressionContext> expCtx(
+ new ExpressionContext(opCtx, std::move(collator), queryRequest.getRuntimeConstants()));
+ expCtx->startExpressionCounters();
+
+ return expCtx;
+}
+
/**
* A command for running .find() queries.
*/
@@ -185,7 +201,7 @@ public:
// Finish the parsing step by using the QueryRequest to create a CanonicalQuery.
const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
- const boost::intrusive_ptr<ExpressionContext> expCtx;
+ auto expCtx = makeExpressionContext(opCtx, *qr);
auto cq = uassertStatusOK(
CanonicalQuery::canonicalize(opCtx,
std::move(qr),
@@ -383,7 +399,7 @@ public:
// Finish the parsing step by using the QueryRequest to create a CanonicalQuery.
const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
- const boost::intrusive_ptr<ExpressionContext> expCtx;
+ auto expCtx = makeExpressionContext(opCtx, *qr);
auto cq = uassertStatusOK(
CanonicalQuery::canonicalize(opCtx,
std::move(qr),
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp
index 915c827e11e..775b3047158 100644
--- a/src/mongo/db/commands/run_aggregate.cpp
+++ b/src/mongo/db/commands/run_aggregate.cpp
@@ -651,7 +651,9 @@ Status runAggregate(OperationContext* opCtx,
invariant(collatorToUse);
expCtx = makeExpressionContext(opCtx, request, std::move(*collatorToUse), uuid);
+ expCtx->startExpressionCounters();
auto pipeline = uassertStatusOK(Pipeline::parse(request.getPipeline(), expCtx));
+ expCtx->stopExpressionCounters();
// Check that the view's collation matches the collation of any views involved in the
// pipeline.
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 30531150031..f8ecefcdc8d 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -78,6 +78,7 @@ env.Library(
'$BUILD_DIR/mongo/db/query/collation/collator_interface',
'$BUILD_DIR/mongo/db/query/query_knobs',
'$BUILD_DIR/mongo/db/pipeline/expression',
+ '$BUILD_DIR/mongo/db/stats/counters',
'$BUILD_DIR/mongo/idl/idl_parser',
'$BUILD_DIR/mongo/util/regex_util',
'$BUILD_DIR/third_party/shim_pcrecpp',
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index 34f3b15c2e4..a20d9d53860 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -725,29 +725,29 @@ bool BitTestMatchExpression::matchesSingleElement(const BSONElement& e,
return performBitTest(eValue);
}
-void BitTestMatchExpression::debugString(StringBuilder& debug, int indentationLevel) const {
- _debugAddSpace(debug, indentationLevel);
-
- debug << path() << " ";
-
+std::string BitTestMatchExpression::name() const {
switch (matchType()) {
case BITS_ALL_SET:
- debug << "$bitsAllSet:";
- break;
+ return "$bitsAllSet";
+
case BITS_ALL_CLEAR:
- debug << "$bitsAllClear:";
- break;
+ return "$bitsAllClear";
+
case BITS_ANY_SET:
- debug << "$bitsAnySet:";
- break;
+ return "$bitsAnySet";
+
case BITS_ANY_CLEAR:
- debug << "$bitsAnyClear:";
- break;
+ return "$bitsAnyClear";
+
default:
MONGO_UNREACHABLE;
}
+}
+
+void BitTestMatchExpression::debugString(StringBuilder& debug, int indentationLevel) const {
+ _debugAddSpace(debug, indentationLevel);
- debug << " [";
+ debug << path() << " " << name() << ": [";
for (size_t i = 0; i < _bitPositions.size(); i++) {
debug << _bitPositions[i];
if (i != _bitPositions.size() - 1) {
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index d4fde5bad93..cdaadd234f1 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -524,6 +524,8 @@ public:
return _bitPositions;
}
+ std::string name() const;
+
private:
ExpressionOptimizerFunc getOptimizer() const final {
return [](std::unique_ptr<MatchExpression> expression) { return expression; };
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 6df5ea05fd1..52fa9248e63 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -65,6 +65,7 @@
#include "mongo/db/matcher/schema/json_schema_parser.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/query/query_knobs_gen.h"
+#include "mongo/db/stats/counters.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/str.h"
#include "mongo/util/string_map.h"
@@ -135,10 +136,13 @@ stdx::function<StatusWithMatchExpression(StringData,
DocumentParseLevel)>
retrievePathlessParser(StringData name);
-StatusWithMatchExpression parseRegexElement(StringData name, BSONElement e) {
+StatusWithMatchExpression parseRegexElement(StringData name,
+ BSONElement e,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx) {
if (e.type() != BSONType::RegEx)
return {Status(ErrorCodes::BadValue, "not a regex")};
+ expCtx->incrementMatchExprCounter("$regex");
return {stdx::make_unique<RegexMatchExpression>(name, e.regex(), e.regexFlags())};
}
@@ -274,6 +278,7 @@ StatusWithMatchExpression parse(const BSONObj& obj,
root->add(parsedExpression.getValue().release());
}
+ expCtx->incrementMatchExprCounter(e.fieldNameStringData());
continue;
}
@@ -291,7 +296,7 @@ StatusWithMatchExpression parse(const BSONObj& obj,
}
if (e.type() == BSONType::RegEx) {
- auto result = parseRegexElement(e.fieldNameStringData(), e);
+ auto result = parseRegexElement(e.fieldNameStringData(), e, expCtx);
if (!result.isOK())
return result;
root->add(result.getValue().release());
@@ -307,6 +312,7 @@ StatusWithMatchExpression parse(const BSONObj& obj,
if (!eq.isOK())
return eq;
+ expCtx->incrementMatchExprCounter("$eq");
root->add(eq.getValue().release());
}
@@ -1025,6 +1031,7 @@ StatusWithMatchExpression parseInternalSchemaMatchArrayIndex(
StatusWithMatchExpression parseGeo(StringData name,
PathAcceptingKeyword type,
const BSONObj& section,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
MatchExpressionParser::AllowedFeatureSet allowedFeatures) {
if (PathAcceptingKeyword::WITHIN == type || PathAcceptingKeyword::GEO_INTERSECTS == type) {
auto gq = stdx::make_unique<GeoExpression>(name.toString());
@@ -1046,6 +1053,7 @@ StatusWithMatchExpression parseGeo(StringData name,
if (!status.isOK()) {
return status;
}
+ expCtx->incrementMatchExprCounter(section.firstElementFieldName());
return {stdx::make_unique<GeoNearMatchExpression>(name, nq.release(), section)};
}
}
@@ -1277,7 +1285,7 @@ StatusWithMatchExpression parseNot(StringData name,
MatchExpressionParser::AllowedFeatureSet allowedFeatures,
DocumentParseLevel currentLevel) {
if (elem.type() == BSONType::RegEx) {
- auto regex = parseRegexElement(name, elem);
+ auto regex = parseRegexElement(name, elem, expCtx);
if (!regex.isOK()) {
return regex;
}
@@ -1493,7 +1501,7 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
case PathAcceptingKeyword::WITHIN:
case PathAcceptingKeyword::GEO_INTERSECTS:
- return parseGeo(name, *parseExpMatchType, context, allowedFeatures);
+ return parseGeo(name, *parseExpMatchType, context, expCtx, allowedFeatures);
case PathAcceptingKeyword::GEO_NEAR:
return {Status(ErrorCodes::BadValue,
@@ -1697,7 +1705,8 @@ Status parseSub(StringData name,
if (firstElt.isABSONObj()) {
if (MatchExpressionParser::parsePathAcceptingKeyword(firstElt) ==
PathAcceptingKeyword::GEO_NEAR) {
- auto s = parseGeo(name, PathAcceptingKeyword::GEO_NEAR, sub, allowedFeatures);
+ auto s =
+ parseGeo(name, PathAcceptingKeyword::GEO_NEAR, sub, expCtx, allowedFeatures);
if (s.isOK()) {
root->add(s.getValue().release());
}
@@ -1714,6 +1723,7 @@ Status parseSub(StringData name,
if (!s.isOK())
return s.getStatus();
+ expCtx->incrementMatchExprCounter(deep.fieldNameStringData());
if (s.getValue())
root->add(s.getValue().release());
}
@@ -1863,6 +1873,35 @@ retrievePathlessParser(StringData name) {
}
return func->second;
}
+
+MONGO_INITIALIZER_WITH_PREREQUISITES(MatchExpressionCounters,
+ ("PathlessOperatorMap", "MatchExpressionParser"))
+(InitializerContext* context) {
+ static const std::set<std::string> exceptionsSet{"within", // deprecated
+ "geoNear", // aggregation stage
+ "db", // $-prefixed field names
+ "id",
+ "ref",
+ "options"};
+
+ for (auto&& [name, keyword] : *queryOperatorMap) {
+ if (name[0] == '_' || exceptionsSet.count(name) > 0) {
+ continue;
+ }
+ operatorCountersMatchExpressions.addMatchExprCounter("$" + name);
+ }
+ for (auto&& [name, fn] : *pathlessOperatorMap) {
+ if (name[0] == '_' || exceptionsSet.count(name) > 0) {
+ continue;
+ }
+ operatorCountersMatchExpressions.addMatchExprCounter("$" + name);
+ }
+ operatorCountersMatchExpressions.addMatchExprCounter("$not");
+ operatorCountersMatchExpressions.addMatchExprCounter("$eq");
+ return Status::OK();
+}
+
+
} // namespace
boost::optional<PathAcceptingKeyword> MatchExpressionParser::parsePathAcceptingKeyword(
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 01b96b37101..de4b1a5fb4c 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -116,6 +116,7 @@ env.Library(
'aggregation_request',
'$BUILD_DIR/mongo/db/query/collation/collator_factory_interface',
'$BUILD_DIR/mongo/db/service_context',
+ '$BUILD_DIR/mongo/db/stats/counters',
'$BUILD_DIR/mongo/util/intrusive_counter',
]
)
diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp
index 781c0cc96c1..1bc7186eec5 100644
--- a/src/mongo/db/pipeline/expression_context.cpp
+++ b/src/mongo/db/pipeline/expression_context.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/pipeline/stub_mongo_process_interface.h"
#include "mongo/db/query/collation/collation_spec.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
+#include "mongo/db/stats/counters.h"
namespace mongo {
@@ -90,6 +91,13 @@ ExpressionContext::ExpressionContext(OperationContext* opCtx,
}
}
+ExpressionContext::ExpressionContext(OperationContext* opCtx,
+ std::unique_ptr<CollatorInterface> collator,
+ const boost::optional<RuntimeConstants>& runtimeConstants)
+ : ExpressionContext(opCtx, collator.get(), runtimeConstants) {
+ _ownedCollator = std::move(collator);
+}
+
ExpressionContext::ExpressionContext(NamespaceString nss,
std::shared_ptr<MongoProcessInterface> processInterface,
const TimeZoneDatabase* tzDb)
@@ -196,4 +204,23 @@ intrusive_ptr<ExpressionContext> ExpressionContext::copyWith(
return expCtx;
}
+void ExpressionContext::startExpressionCounters() {
+ if (!_expressionCounters) {
+ _expressionCounters = boost::make_optional<ExpressionCounters>({});
+ }
+}
+
+void ExpressionContext::incrementMatchExprCounter(StringData name) {
+ if (_expressionCounters) {
+ ++_expressionCounters->matchExprCountersMap[name];
+ }
+}
+
+void ExpressionContext::stopExpressionCounters() {
+ if (_expressionCounters) {
+ operatorCountersMatchExpressions.mergeCounters(_expressionCounters->matchExprCountersMap);
+ }
+ _expressionCounters = {};
+}
+
} // namespace mongo
diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h
index e55284315f0..8717d4d518c 100644
--- a/src/mongo/db/pipeline/expression_context.h
+++ b/src/mongo/db/pipeline/expression_context.h
@@ -54,6 +54,15 @@
namespace mongo {
+/**
+ * The structure ExpressionCounters encapsulates counters for match, aggregate, and other
+ * expression types as seen in the end-user queries.
+ */
+struct ExpressionCounters {
+ StringMap<uint64_t> aggExprCountersMap;
+ StringMap<uint64_t> matchExprCountersMap;
+};
+
class ExpressionContext : public RefCountable {
public:
struct ResolvedNamespace {
@@ -116,6 +125,12 @@ public:
const boost::optional<RuntimeConstants>& runtimeConstants = boost::none);
/**
+ * Constructs an ExpressionContext suitable for find which owns the collator.
+ */
+ ExpressionContext(OperationContext* opCtx,
+ std::unique_ptr<CollatorInterface> collator,
+ const boost::optional<RuntimeConstants>& runtimeConstants = boost::none);
+ /**
* Used by a pipeline to check for interrupts so that killOp() works. Throws a UserAssertion if
* this aggregation pipeline has been interrupted.
*/
@@ -197,6 +212,23 @@ public:
auto getRuntimeConstants() const {
return variables.getRuntimeConstants();
}
+
+ /**
+ * Create optional internal expression counters and start counting.
+ */
+ void startExpressionCounters();
+
+ /**
+ * Increment the counter for the match expression with a given name.
+ */
+ void incrementMatchExprCounter(StringData name);
+
+ /**
+ * Merge expression counters from the current expression context into the global maps
+ * and stop counting.
+ */
+ void stopExpressionCounters();
+
// The explain verbosity requested by the user, or boost::none if no explain was requested.
boost::optional<ExplainOptions::Verbosity> explain;
@@ -288,6 +320,9 @@ protected:
StringMap<ResolvedNamespace> _resolvedNamespaces;
int _interruptCounter = kInterruptCheckPeriod;
+
+private:
+ boost::optional<ExpressionCounters> _expressionCounters = boost::none;
};
} // namespace mongo
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index 02a1848d4bd..fb3507177f2 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -162,6 +162,11 @@ StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize(
if (!statusWithMatcher.isOK()) {
return statusWithMatcher.getStatus();
}
+
+ // Stop counting match expressions after they have been parsed to exclude expressions created
+ // during optimization and other processing steps.
+ newExpCtx->stopExpressionCounters();
+
std::unique_ptr<MatchExpression> me = std::move(statusWithMatcher.getValue());
// Make the CQ we'll hopefully return.
diff --git a/src/mongo/db/stats/SConscript b/src/mongo/db/stats/SConscript
index 6dc104dbe65..dad5d94c6f9 100644
--- a/src/mongo/db/stats/SConscript
+++ b/src/mongo/db/stats/SConscript
@@ -63,6 +63,7 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/commands/server_status_core',
'$BUILD_DIR/mongo/util/concurrency/spin_lock',
],
)
diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp
index a633f4c516e..dd8ed01fcc3 100644
--- a/src/mongo/db/stats/counters.cpp
+++ b/src/mongo/db/stats/counters.cpp
@@ -160,4 +160,5 @@ OpCounters globalOpCounters;
OpCounters replOpCounters;
NetworkCounter networkCounter;
AggStageCounters aggStageCounters;
+OperatorCountersMatchExpressions operatorCountersMatchExpressions;
} // namespace mongo
diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h
index 25e39c2407d..c0a3e1749e5 100644
--- a/src/mongo/db/stats/counters.h
+++ b/src/mongo/db/stats/counters.h
@@ -190,4 +190,37 @@ public:
};
extern AggStageCounters aggStageCounters;
+
+/**
+ * Global counters for match expressions.
+ */
+class OperatorCountersMatchExpressions {
+private:
+ struct MatchExprCounter {
+ MatchExprCounter(StringData name) : metric("operatorCounters.match." + name, &counter) {}
+
+ Counter64 counter;
+ ServerStatusMetricField<Counter64> metric;
+ };
+
+public:
+ void addMatchExprCounter(StringData name) {
+ operatorCountersMatchExprMap[name] = std::make_unique<MatchExprCounter>(name);
+ }
+
+ void mergeCounters(StringMap<uint64_t>& toMerge) {
+ for (auto&& [name, cnt] : toMerge) {
+ if (auto it = operatorCountersMatchExprMap.find(name);
+ it != operatorCountersMatchExprMap.end()) {
+ it->second->counter.increment(cnt);
+ }
+ }
+ }
+
+private:
+ // Map of match expressions to the number of occurrences in queries.
+ StringMap<std::unique_ptr<MatchExprCounter>> operatorCountersMatchExprMap = {};
+};
+
+extern OperatorCountersMatchExpressions operatorCountersMatchExpressions;
} // namespace mongo