diff options
author | Milena Ivanova <milena.ivanova@mongodb.com> | 2021-11-23 14:47:17 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-11-23 15:23:15 +0000 |
commit | caa1e34fe651d74808b9ebe2785c660fd3da20d9 (patch) | |
tree | 4ee2b1db511a745a252539b89d1d52faa467de31 | |
parent | 2fc19024b2c270e027469bfe499bf74817d7d91b (diff) | |
download | mongo-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.js | 285 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/commands/run_aggregate.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.h | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 49 | ||||
-rw-r--r-- | src/mongo/db/pipeline/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.h | 35 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/stats/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.h | 33 |
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 |