summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/serverstatus_index_stats.js
diff options
context:
space:
mode:
authorLouis Williams <louis.williams@mongodb.com>2022-03-29 13:36:33 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-20 16:07:06 +0000
commit5c4ea8bae7c97955962fa4f825a0ed9ce00d77ed (patch)
tree9a92e98b6ad74093c70967263da7a1dd4123321b /jstests/noPassthrough/serverstatus_index_stats.js
parent43253c80d557e82f96c175d01c95953d506cceda (diff)
downloadmongo-5c4ea8bae7c97955962fa4f825a0ed9ce00d77ed.tar.gz
SERVER-63254 Add index feature usage stats to serverStatus
(cherry picked from commit 0fa0170045ccab649621111ec86c80e798bcac23) (cherry picked from commit 45def0471022bb6e7a56acb354e35817211a25d8)
Diffstat (limited to 'jstests/noPassthrough/serverstatus_index_stats.js')
-rw-r--r--jstests/noPassthrough/serverstatus_index_stats.js222
1 files changed, 222 insertions, 0 deletions
diff --git a/jstests/noPassthrough/serverstatus_index_stats.js b/jstests/noPassthrough/serverstatus_index_stats.js
new file mode 100644
index 00000000000..a1fc286e8e1
--- /dev/null
+++ b/jstests/noPassthrough/serverstatus_index_stats.js
@@ -0,0 +1,222 @@
+/**
+ * Tests that serverStatus contains an indexStats section. This section reports globally-aggregated
+ * statistics about features in use by indexes and how often they are used.
+ *
+ * @tags: [
+ * requires_persistence,
+ * requires_replication,
+ * ]
+ */
+(function() {
+"use strict";
+
+const assertStats = (db, assertFn) => {
+ const stats = db.serverStatus().indexStats;
+ try {
+ assertFn(stats);
+ } catch (e) {
+ print("result: " + tojson(stats));
+ throw e;
+ }
+};
+
+// If new features are added, they must also be added to this list or the test will fail.
+const knownFeatures = [
+ "2d",
+ "2dsphere",
+ "collation",
+ "compound",
+ "hashed",
+ "id",
+ "normal",
+ "partial",
+ "single",
+ "sparse",
+ "text",
+ "ttl",
+ "unique",
+ "wildcard",
+];
+
+const assertZeroCounts = (db) => {
+ assertStats(db, (featureStats) => {
+ assert.eq(featureStats.count, 0);
+ for (const [feature, stats] of Object.entries(featureStats.features)) {
+ assert.contains(feature, knownFeatures, "unknown feature reported by indexStats");
+ assert.eq(0, stats.count, feature);
+ }
+ });
+};
+
+const assertZeroAccess = (db) => {
+ assertStats(db, (featureStats) => {
+ for (const [feature, stats] of Object.entries(featureStats.features)) {
+ assert.contains(feature, knownFeatures, "unknown feature reported by indexStats");
+ assert.eq(0, stats.accesses, feature);
+ }
+ });
+};
+
+const assertCountIncrease = (last, current, inc) => {
+ assert.eq(last.count + inc, current.count, "incorrect index count");
+};
+
+const assertFeatureCountIncrease = (last, current, feature, inc) => {
+ assert.eq(last.features[feature].count + inc,
+ current.features[feature].count,
+ "incorrect feature count for " + feature);
+};
+
+const assertFeatureAccessIncrease = (last, current, feature, inc) => {
+ assert.eq(last.features[feature].accesses + inc,
+ current.features[feature].accesses,
+ "incorrect feature accesses for " + feature);
+};
+
+const replSet = new ReplSetTest({nodes: 1});
+replSet.startSet();
+replSet.initiate();
+
+let primary = replSet.getPrimary();
+let db = primary.getDB('test');
+
+assertZeroCounts(db);
+assertZeroAccess(db);
+
+let lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(db.testColl.createIndex({twoD: '2d', b: 1}, {unique: true, sparse: true}));
+assert.commandWorked(db.testColl.insert({twoD: [0, 0], b: 1}));
+assert.eq(1, db.testColl.find({twoD: {$geoNear: [0, 0]}}).itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 2);
+ assertFeatureCountIncrease(lastStats, stats, '2d', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'compound', 1);
+ // The index build implicitly created the collection, which also builds an _id index.
+ assertFeatureCountIncrease(lastStats, stats, 'id', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'sparse', 1);
+ // Note that the _id index is not included in this unique counter. This is due to a quirk in the
+ // _id index spec that does not actually have a unique:true property.
+ assertFeatureCountIncrease(lastStats, stats, 'unique', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, '2d', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'compound', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'id', 0);
+ assertFeatureAccessIncrease(lastStats, stats, 'sparse', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'unique', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(db.testColl.createIndex({sphere: '2dsphere'}));
+assert.commandWorked(db.testColl.insert({sphere: {type: "Point", coordinates: [0, 0]}}));
+assert.eq(1,
+ db.testColl
+ .aggregate([{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1, 1]},
+ key: 'sphere',
+ distanceField: 'dist',
+ }
+ }])
+ .itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 1);
+ assertFeatureCountIncrease(lastStats, stats, '2dsphere', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'single', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, '2dsphere', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'single', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(
+ db.testColl.createIndex({hashed: 'hashed', p: 1}, {partialFilterExpression: {p: 1}}));
+assert.commandWorked(db.testColl.insert({hashed: 1, p: 1}));
+assert.eq(1, db.testColl.find({hashed: 1}).hint({hashed: 'hashed', p: 1}).itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 1);
+ assertFeatureCountIncrease(lastStats, stats, 'compound', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'hashed', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'partial', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, 'compound', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'hashed', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'partial', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(
+ db.testColl.createIndex({a: 1}, {expireAfterSeconds: 3600, collation: {locale: 'en'}}));
+let now = new Date();
+assert.commandWorked(db.testColl.insert({a: now}));
+assert.eq(1, db.testColl.find({a: now}).itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 1);
+ assertFeatureCountIncrease(lastStats, stats, 'collation', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'normal', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'single', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'ttl', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, 'collation', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'normal', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'single', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'ttl', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(db.testColl.createIndex({text: 'text'}));
+assert.commandWorked(db.testColl.insert({text: "a string"}));
+assert.eq(1, db.testColl.find({$text: {$search: "string"}}).itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 1);
+ // Text indexes are internally compound, but that should not be reflected in the stats.
+ assertFeatureCountIncrease(lastStats, stats, 'compound', 0);
+ assertFeatureCountIncrease(lastStats, stats, 'text', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, 'compound', 0);
+ assertFeatureAccessIncrease(lastStats, stats, 'text', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+assert.commandWorked(db.testColl.createIndex({'wild.$**': 1}));
+assert.commandWorked(db.testColl.insert({wild: {a: 1}}));
+assert.eq(1, db.testColl.find({'wild.a': 1}).itcount());
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 1);
+ assertFeatureCountIncrease(lastStats, stats, 'single', 1);
+ assertFeatureCountIncrease(lastStats, stats, 'wildcard', 1);
+
+ assertFeatureAccessIncrease(lastStats, stats, 'single', 1);
+ assertFeatureAccessIncrease(lastStats, stats, 'wildcard', 1);
+});
+
+lastStats = db.serverStatus().indexStats;
+
+// After restarting the server, we expect all of the access counters to reset to zero, but that the
+// feature counters remain the same as before startup.
+replSet.stopSet(undefined, /* restart */ true);
+replSet.startSet({}, /* restart */ true);
+primary = replSet.getPrimary();
+db = primary.getDB('test');
+
+assertZeroAccess(db);
+assertStats(db, (stats) => {
+ assertCountIncrease(lastStats, stats, 0);
+
+ const features = stats.features;
+ for (const [feature, _] of Object.entries(features)) {
+ assert.contains(feature, knownFeatures);
+ assertFeatureCountIncrease(lastStats, stats, feature, 0);
+ }
+});
+
+assert.commandWorked(db.dropDatabase());
+assertZeroCounts(db);
+
+replSet.stopSet();
+})();