diff options
author | Milena Ivanova <milena.ivanova@mongodb.com> | 2021-05-28 09:24:11 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-08-05 08:24:59 +0000 |
commit | 98b4a0a2b2c4a230efe1c2ddc3da487a575d670d (patch) | |
tree | 7269ee48a69bfaea61b77a8d3f78bc072735b3b0 | |
parent | 85976973ce08a8d7dedbcc108d4ace00c25c9b04 (diff) | |
download | mongo-98b4a0a2b2c4a230efe1c2ddc3da487a575d670d.tar.gz |
SERVER-56602 Track usage of match expressions in serverStatus
-rw-r--r-- | jstests/noPassthrough/operator_counters_match.js | 289 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/run_aggregate.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/curop.h | 11 | ||||
-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 | 39 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.h | 28 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.h | 33 |
13 files changed, 445 insertions, 17 deletions
diff --git a/jstests/noPassthrough/operator_counters_match.js b/jstests/noPassthrough/operator_counters_match.js new file mode 100644 index 00000000000..f1f18222dfe --- /dev/null +++ b/jstests/noPassthrough/operator_counters_match.js @@ -0,0 +1,289 @@ +/** + * Tests counters for match expressions. + * @tags: [requires_fcv_50] + */ + +(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 $rand, $lt, and $add are recorded in +// operatorCounters.expressions metrics. +checkCounters( + () => assert.eq(4, coll.find({$expr: {$lt: ["$a", {$add: [{$rand: {}}, 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"); + +pipeline = [{$match: {$sampleRate: 0.25}}]; +checkCounters(() => assert.lte(0, coll.aggregate(pipeline).itcount()), "$sampleRate"); + +// Invalid expressions do not increment any counter. +checkCountersWithError( + () => coll.aggregate([{$match: {$and: [{b: /^fo/}, {a: {$mod: [2, 'z']}}]}}]).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 9dd3393360e..31faa7c7bb3 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -158,6 +158,8 @@ boost::intrusive_ptr<ExpressionContext> makeExpressionContext( CurOp::get(opCtx)->dbProfileLevel() > 0 // mayDbProfile ); expCtx->tempDir = storageGlobalParams.dbpath + "/_tmp"; + expCtx->startExpressionCounters(); + return expCtx; } diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index 789581c7337..a25075c2839 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -764,7 +764,9 @@ Status runAggregate(OperationContext* opCtx, expCtx = makeExpressionContext( opCtx, request, std::move(*collatorToUse), uuid, collatorToUseMatchesDefault); + expCtx->startExpressionCounters(); auto pipeline = 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/curop.h b/src/mongo/db/curop.h index 8c3adec3a91..afb48de2440 100644 --- a/src/mongo/db/curop.h +++ b/src/mongo/db/curop.h @@ -775,6 +775,16 @@ public: _tickSource = tickSource; } + /** + * Merge match counters from the current operation into the global map and stop counting. + */ + void stopMatchExprCounter(); + + /** + * Increment the counter for the match expression with given name in the current operation. + */ + void incrementMatchExprCounter(StringData name); + private: class CurOpStack; @@ -846,4 +856,5 @@ private: TickSource* _tickSource = nullptr; }; + } // namespace mongo diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index aa1badd3cb7..f7b42816cb8 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -70,6 +70,7 @@ env.Library( '$BUILD_DIR/mongo/db/pipeline/expression_context', '$BUILD_DIR/mongo/db/query/collation/collator_interface', '$BUILD_DIR/mongo/db/query/query_knobs', + '$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 c1f9a9ebb44..8f000b52728 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -778,29 +778,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 248e68f1f44..f2c3a5f13a3 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -693,6 +693,8 @@ public: return _bitMask; } + 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 8839b537f4c..06016d8c3a4 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -69,6 +69,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/query/dbref.h" #include "mongo/db/query/query_knobs_gen.h" +#include "mongo/db/stats/counters.h" #include "mongo/util/str.h" #include "mongo/util/string_map.h" @@ -173,6 +174,7 @@ StatusWithMatchExpression parseRegexElement(StringData name, if (e.type() != BSONType::RegEx) return {Status(ErrorCodes::BadValue, "not a regex")}; + expCtx->incrementMatchExprCounter("$regex"); return {std::make_unique<RegexMatchExpression>( name, e.regex(), @@ -319,6 +321,7 @@ StatusWithMatchExpression parse(const BSONObj& obj, if (auto&& expr = parsedExpression.getValue()) root->add(std::move(expr)); + expCtx->incrementMatchExprCounter(e.fieldNameStringData()); continue; } @@ -345,7 +348,6 @@ StatusWithMatchExpression parse(const BSONObj& obj, auto result = parseRegexElement(e.fieldNameStringData(), e, expCtx); if (!result.isOK()) return result; - addExpressionToRoot(expCtx, root.get(), std::move(result.getValue())); continue; } @@ -361,7 +363,7 @@ StatusWithMatchExpression parse(const BSONObj& obj, allowedFeatures); if (!eq.isOK()) return eq; - + expCtx->incrementMatchExprCounter("$eq"); addExpressionToRoot(expCtx, root.get(), std::move(eq.getValue())); } @@ -423,7 +425,9 @@ StatusWithMatchExpression parseSampleRate(StringData name, // DeMorgan's law here you will be suprised that $sampleRate will accept NaN as a valid // argument. return {Status(ErrorCodes::BadValue, "numeric argument to $sampleRate must be in [0, 1]")}; - } else if (x == kRandomMinValue) { + } + + if (x == kRandomMinValue) { return std::make_unique<ExprMatchExpression>( ExpressionConstant::create(expCtx.get(), Value(false)), expCtx, @@ -1202,6 +1206,7 @@ StatusWithMatchExpression parseGeo(StringData name, return status; } expCtx->sbeCompatible = false; + expCtx->incrementMatchExprCounter(section.firstElementFieldName()); return {std::make_unique<GeoNearMatchExpression>(name, nq.release(), section)}; } } @@ -2017,6 +2022,7 @@ Status parseSub(StringData name, if (!s.isOK()) return s.getStatus(); + expCtx->incrementMatchExprCounter(deep.fieldNameStringData()); if (s.getValue()) { addExpressionToRoot(expCtx, root, std::move(s.getValue())); } @@ -2179,6 +2185,33 @@ 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"); +} + + } // namespace boost::optional<PathAcceptingKeyword> MatchExpressionParser::parsePathAcceptingKeyword( diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp index c4cb0f6e411..69698ab0289 100644 --- a/src/mongo/db/pipeline/expression_context.cpp +++ b/src/mongo/db/pipeline/expression_context.cpp @@ -36,6 +36,7 @@ #include "mongo/db/pipeline/process_interface/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" #include "mongo/util/intrusive_counter.h" namespace mongo { @@ -223,4 +224,24 @@ 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.get().matchExprCountersMap[name]; + } +} + +void ExpressionContext::stopExpressionCounters() { + if (_expressionCounters) { + operatorCountersMatchExpressions.mergeCounters( + _expressionCounters.get().matchExprCountersMap); + } + _expressionCounters = boost::none; +} + } // namespace mongo diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h index b40028dc30a..398a162b283 100644 --- a/src/mongo/db/pipeline/expression_context.h +++ b/src/mongo/db/pipeline/expression_context.h @@ -58,6 +58,15 @@ namespace mongo { class AggregateCommandRequest; +/** + * 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: static constexpr size_t kMaxSubPipelineViewDepth = 20; @@ -316,6 +325,22 @@ public: return JsExecution::get(opCtx, scopeObj, ns.db(), loadStoredProcedures, jsHeapLimitMB); } + /** + * 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; @@ -413,6 +438,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 de2e39895ff..87efc4cc259 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -126,6 +126,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()); Status initStatus = diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp index 1cf3e4f4f72..2f9b070b888 100644 --- a/src/mongo/db/stats/counters.cpp +++ b/src/mongo/db/stats/counters.cpp @@ -314,4 +314,5 @@ AuthCounter authCounter; AggStageCounters aggStageCounters; DotsAndDollarsFieldsCounters dotsAndDollarsFieldsCounters; OperatorCountersExpressions operatorCountersExpressions; +OperatorCountersMatchExpressions operatorCountersMatchExpressions; } // namespace mongo diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h index 397951bdd7b..18e39b1366a 100644 --- a/src/mongo/db/stats/counters.h +++ b/src/mongo/db/stats/counters.h @@ -363,4 +363,37 @@ private: }; extern OperatorCountersExpressions operatorCountersExpressions; + +/** + * 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 |