summaryrefslogtreecommitdiff
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
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)
-rw-r--r--jstests/noPassthrough/serverstatus_index_stats.js222
-rw-r--r--src/mongo/db/SConscript6
-rw-r--r--src/mongo/db/catalog/index_build_block.cpp4
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.cpp27
-rw-r--r--src/mongo/db/collection_index_usage_tracker.cpp32
-rw-r--r--src/mongo/db/collection_index_usage_tracker.h24
-rw-r--r--src/mongo/db/collection_index_usage_tracker_test.cpp154
-rw-r--r--src/mongo/db/global_index_usage_tracker.cpp223
-rw-r--r--src/mongo/db/global_index_usage_tracker.h132
-rw-r--r--src/mongo/db/query/collection_index_usage_tracker_decoration.cpp3
-rw-r--r--src/mongo/db/query/collection_query_info.cpp4
11 files changed, 792 insertions, 39 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();
+})();
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 49969c20fc7..be5b6162d8b 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -243,12 +243,16 @@ env.Library(
env.Library(
target='collection_index_usage_tracker',
source=[
- 'collection_index_usage_tracker.cpp'
+ 'collection_index_usage_tracker.cpp',
+ 'global_index_usage_tracker.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/db/commands/server_status_core',
],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/index_names',
+ ]
)
# This library exists because some libraries, such as our networking library, need access to server
diff --git a/src/mongo/db/catalog/index_build_block.cpp b/src/mongo/db/catalog/index_build_block.cpp
index 80a45ad0f56..ac29eccbc29 100644
--- a/src/mongo/db/catalog/index_build_block.cpp
+++ b/src/mongo/db/catalog/index_build_block.cpp
@@ -82,7 +82,9 @@ void IndexBuildBlock::_completeInit(OperationContext* opCtx, Collection* collect
auto desc = getEntry(opCtx, collection)->descriptor();
CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection);
CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations())
- .registerIndex(desc->indexName(), desc->keyPattern());
+ .registerIndex(desc->indexName(),
+ desc->keyPattern(),
+ IndexFeatures::make(desc, collection->ns().isOnInternalDb()));
}
Status IndexBuildBlock::initForResume(OperationContext* opCtx,
diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp
index a090f88ce14..371680f0af8 100644
--- a/src/mongo/db/catalog/index_catalog_impl.cpp
+++ b/src/mongo/db/catalog/index_catalog_impl.cpp
@@ -1048,9 +1048,11 @@ namespace {
class IndexRemoveChange final : public RecoveryUnit::Change {
public:
IndexRemoveChange(IndexCatalogEntryContainer* entries,
+ const NamespaceString& nss,
std::shared_ptr<IndexCatalogEntry> entry,
SharedCollectionDecorations* collectionDecorations)
: _entries(entries),
+ _nss(nss),
_entry(std::move(entry)),
_collectionDecorations(collectionDecorations) {}
@@ -1064,11 +1066,14 @@ public:
// Refresh the CollectionIndexUsageTrackerDecoration's knowledge of what indices are
// present as it is shared state across Collection copies.
CollectionIndexUsageTrackerDecoration::get(_collectionDecorations)
- .registerIndex(indexDescriptor->indexName(), indexDescriptor->keyPattern());
+ .registerIndex(indexDescriptor->indexName(),
+ indexDescriptor->keyPattern(),
+ IndexFeatures::make(indexDescriptor, _nss.isOnInternalDb()));
}
private:
IndexCatalogEntryContainer* _entries;
+ const NamespaceString _nss;
std::shared_ptr<IndexCatalogEntry> _entry;
SharedCollectionDecorations* _collectionDecorations;
};
@@ -1087,13 +1092,19 @@ Status IndexCatalogImpl::dropIndexEntry(OperationContext* opCtx,
auto released = _readyIndexes.release(entry->descriptor());
if (released) {
invariant(released.get() == entry);
- opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>(
- &_readyIndexes, std::move(released), collection->getSharedDecorations()));
+ opCtx->recoveryUnit()->registerChange(
+ std::make_unique<IndexRemoveChange>(&_readyIndexes,
+ collection->ns(),
+ std::move(released),
+ collection->getSharedDecorations()));
} else {
released = _buildingIndexes.release(entry->descriptor());
invariant(released.get() == entry);
- opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>(
- &_buildingIndexes, std::move(released), collection->getSharedDecorations()));
+ opCtx->recoveryUnit()->registerChange(
+ std::make_unique<IndexRemoveChange>(&_buildingIndexes,
+ collection->ns(),
+ std::move(released),
+ collection->getSharedDecorations()));
}
CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection);
@@ -1278,7 +1289,7 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx,
auto oldEntry = _readyIndexes.release(oldDesc);
invariant(oldEntry);
opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>(
- &_readyIndexes, std::move(oldEntry), collection->getSharedDecorations()));
+ &_readyIndexes, collection->ns(), std::move(oldEntry), collection->getSharedDecorations()));
CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations())
.unregisterIndex(indexName);
@@ -1294,7 +1305,9 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx,
invariant(newEntry->isReady(opCtx, collection));
auto desc = newEntry->descriptor();
CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations())
- .registerIndex(desc->indexName(), desc->keyPattern());
+ .registerIndex(desc->indexName(),
+ desc->keyPattern(),
+ IndexFeatures::make(desc, collection->ns().isOnInternalDb()));
// Last rebuild index data for CollectionQueryInfo for this Collection.
CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection);
diff --git a/src/mongo/db/collection_index_usage_tracker.cpp b/src/mongo/db/collection_index_usage_tracker.cpp
index 32df44e98f2..168e8766be1 100644
--- a/src/mongo/db/collection_index_usage_tracker.cpp
+++ b/src/mongo/db/collection_index_usage_tracker.cpp
@@ -37,6 +37,7 @@
#include "mongo/base/counter.h"
#include "mongo/db/commands/server_status_metric.h"
+#include "mongo/db/index/index_descriptor.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/clock_source.h"
@@ -49,10 +50,14 @@ ServerStatusMetricField<Counter64> displayCollectionScans("queryExecutor.collect
&collectionScansCounter);
ServerStatusMetricField<Counter64> displayCollectionScansNonTailable(
"queryExecutor.collectionScans.nonTailable", &collectionScansNonTailableCounter);
+
} // namespace
-CollectionIndexUsageTracker::CollectionIndexUsageTracker(ClockSource* clockSource)
- : _indexUsageStatsMap(std::make_shared<CollectionIndexUsageMap>()), _clockSource(clockSource) {
+CollectionIndexUsageTracker::CollectionIndexUsageTracker(
+ GlobalIndexUsageTracker* globalIndexUsageTracker, ClockSource* clockSource)
+ : _indexUsageStatsMap(std::make_shared<CollectionIndexUsageMap>()),
+ _clockSource(clockSource),
+ _globalIndexUsageTracker(globalIndexUsageTracker) {
invariant(_clockSource);
}
@@ -69,6 +74,8 @@ void CollectionIndexUsageTracker::recordIndexAccess(StringData indexName) {
return;
}
+ _globalIndexUsageTracker->onAccess(it->second->features);
+
// Increment the index usage atomic counter.
it->second->accesses.fetchAndAdd(1);
}
@@ -84,7 +91,9 @@ void CollectionIndexUsageTracker::recordCollectionScansNonTailable(
collectionScansNonTailableCounter.increment(collectionScansNonTailable);
}
-void CollectionIndexUsageTracker::registerIndex(StringData indexName, const BSONObj& indexKey) {
+void CollectionIndexUsageTracker::registerIndex(StringData indexName,
+ const BSONObj& indexKey,
+ const IndexFeatures& features) {
invariant(!indexName.empty());
// Create a copy of the map to modify.
@@ -95,9 +104,11 @@ void CollectionIndexUsageTracker::registerIndex(StringData indexName, const BSON
// Create the map entry.
auto inserted = mapCopy->try_emplace(
- indexName, make_intrusive<IndexUsageStats>(_clockSource->now(), indexKey));
+ indexName, make_intrusive<IndexUsageStats>(_clockSource->now(), indexKey, features));
invariant(inserted.second);
+ _globalIndexUsageTracker->onRegister(inserted.first->second->features);
+
// Swap the modified map into place atomically.
atomic_store(&_indexUsageStatsMap, std::move(mapCopy));
}
@@ -109,11 +120,16 @@ void CollectionIndexUsageTracker::unregisterIndex(StringData indexName) {
auto mapSharedPtr = atomic_load(&_indexUsageStatsMap);
auto mapCopy = std::make_shared<CollectionIndexUsageMap>(*mapSharedPtr);
- // Remove the map entry.
- mapCopy->erase(indexName);
+ auto it = mapCopy->find(indexName);
+ if (it != mapCopy->end()) {
+ _globalIndexUsageTracker->onUnregister(it->second->features);
- // Swap the modified map into place atomically.
- atomic_store(&_indexUsageStatsMap, std::move(mapCopy));
+ // Remove the map entry.
+ mapCopy->erase(it);
+
+ // Swap the modified map into place atomically.
+ atomic_store(&_indexUsageStatsMap, std::move(mapCopy));
+ }
}
std::shared_ptr<CollectionIndexUsageTracker::CollectionIndexUsageMap>
diff --git a/src/mongo/db/collection_index_usage_tracker.h b/src/mongo/db/collection_index_usage_tracker.h
index c51ffebdaa5..84ee2230f4f 100644
--- a/src/mongo/db/collection_index_usage_tracker.h
+++ b/src/mongo/db/collection_index_usage_tracker.h
@@ -33,6 +33,7 @@
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/db/global_index_usage_tracker.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/string_map.h"
@@ -41,6 +42,7 @@
namespace mongo {
class ClockSource;
+class ServiceContext;
/**
* CollectionIndexUsageTracker tracks index usage statistics for a collection. An index is
@@ -68,18 +70,20 @@ public:
struct IndexUsageStats : public RefCountable {
IndexUsageStats() = default;
- explicit IndexUsageStats(Date_t now, const BSONObj& key)
- : trackerStartTime(now), indexKey(key.getOwned()) {}
+ explicit IndexUsageStats(Date_t now, const BSONObj& key, const IndexFeatures& idxFeatures)
+ : trackerStartTime(now), indexKey(key.getOwned()), features(idxFeatures) {}
IndexUsageStats(const IndexUsageStats& other)
: accesses(other.accesses.load()),
trackerStartTime(other.trackerStartTime),
- indexKey(other.indexKey) {}
+ indexKey(other.indexKey),
+ features(other.features) {}
IndexUsageStats& operator=(const IndexUsageStats& other) {
accesses.store(other.accesses.load());
trackerStartTime = other.trackerStartTime;
indexKey = other.indexKey;
+ features = other.features;
return *this;
}
@@ -91,6 +95,9 @@ public:
// An owned copy of the associated IndexDescriptor's index key.
BSONObj indexKey;
+
+ // Features in use by this index for global feature usage tracking.
+ IndexFeatures features;
};
/**
@@ -107,7 +114,8 @@ public:
* Does not take ownership of 'clockSource'. 'clockSource' must refer to a non-null clock
* source that is valid for the lifetime of the constructed CollectionIndexUsageTracker.
*/
- explicit CollectionIndexUsageTracker(ClockSource* clockSource);
+ explicit CollectionIndexUsageTracker(GlobalIndexUsageTracker* globalTracker,
+ ClockSource* clockSource);
/**
* Record that an operation used index 'indexName'. Safe to be called by multiple threads
@@ -121,7 +129,9 @@ public:
* Must be called under an exclusive collection lock in order to serialize calls to
* registerIndex() and unregisterIndex().
*/
- void registerIndex(StringData indexName, const BSONObj& indexKey);
+ void registerIndex(StringData indexName,
+ const BSONObj& indexKey,
+ const IndexFeatures& features);
/**
* Erase statistics for index 'indexName'. Can be safely called even if indexName is not
@@ -167,6 +177,10 @@ private:
// be set.
ClockSource* _clockSource;
+ // All CollectionIndexUsageTrackers also update the GlobalIndexUsageTracker to report global
+ // index statistics for the server.
+ GlobalIndexUsageTracker* _globalIndexUsageTracker;
+
AtomicWord<unsigned long long> _collectionScans{0};
AtomicWord<unsigned long long> _collectionScansNonTailable{0};
};
diff --git a/src/mongo/db/collection_index_usage_tracker_test.cpp b/src/mongo/db/collection_index_usage_tracker_test.cpp
index 492c8cb4612..a532e91a152 100644
--- a/src/mongo/db/collection_index_usage_tracker_test.cpp
+++ b/src/mongo/db/collection_index_usage_tracker_test.cpp
@@ -30,6 +30,7 @@
#include "mongo/platform/basic.h"
#include "mongo/db/collection_index_usage_tracker.h"
+#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/jsobj.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/clock_source_mock.h"
@@ -39,7 +40,7 @@ namespace {
class CollectionIndexUsageTrackerTest : public unittest::Test {
protected:
- CollectionIndexUsageTrackerTest() : _tracker(&_clockSource) {}
+ CollectionIndexUsageTrackerTest() : _tracker(&_globalIndexUsage, &_clockSource) {}
/**
* Returns an unowned pointer to the tracker owned by this test fixture.
@@ -55,7 +56,12 @@ protected:
return &_clockSource;
}
+ GlobalIndexUsageTracker* getGlobalIndexUsage() {
+ return &_globalIndexUsage;
+ }
+
private:
+ GlobalIndexUsageTracker _globalIndexUsage;
ClockSourceMock _clockSource;
CollectionIndexUsageTracker _tracker;
};
@@ -67,7 +73,7 @@ TEST_F(CollectionIndexUsageTrackerTest, Empty) {
// Test that recording of a single index hit is reflected in returned stats map.
TEST_F(CollectionIndexUsageTrackerTest, SingleHit) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
auto statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
@@ -76,7 +82,7 @@ TEST_F(CollectionIndexUsageTrackerTest, SingleHit) {
// Test that recording of multiple index hits are reflected in stats map.
TEST_F(CollectionIndexUsageTrackerTest, MultipleHit) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
getTracker()->recordIndexAccess("foo");
auto statsMap = getTracker()->getUsageStats();
@@ -86,7 +92,7 @@ TEST_F(CollectionIndexUsageTrackerTest, MultipleHit) {
// Test that an index is registered correctly with indexKey.
TEST_F(CollectionIndexUsageTrackerTest, IndexKey) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
auto statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
ASSERT_BSONOBJ_EQ(BSON("foo" << 1), statsMap->at("foo")->indexKey);
@@ -94,16 +100,16 @@ TEST_F(CollectionIndexUsageTrackerTest, IndexKey) {
// Test that index registration generates an entry in the stats map.
TEST_F(CollectionIndexUsageTrackerTest, Register) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
ASSERT_EQUALS(1U, getTracker()->getUsageStats()->size());
- getTracker()->registerIndex("bar", BSON("bar" << 1));
+ getTracker()->registerIndex("bar", BSON("bar" << 1), {});
ASSERT_EQUALS(2U, getTracker()->getUsageStats()->size());
}
// Test that index deregistration results in removal of an entry from the stats map.
TEST_F(CollectionIndexUsageTrackerTest, Deregister) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
- getTracker()->registerIndex("bar", BSON("bar" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
+ getTracker()->registerIndex("bar", BSON("bar" << 1), {});
ASSERT_EQUALS(2U, getTracker()->getUsageStats()->size());
getTracker()->unregisterIndex("foo");
ASSERT_EQUALS(1U, getTracker()->getUsageStats()->size());
@@ -113,7 +119,7 @@ TEST_F(CollectionIndexUsageTrackerTest, Deregister) {
// Test that index deregistration results in reset of the usage counter.
TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
getTracker()->recordIndexAccess("foo");
auto statsMap = getTracker()->getUsageStats();
@@ -124,7 +130,7 @@ TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) {
statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") == statsMap->end());
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
@@ -133,7 +139,7 @@ TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) {
// Test that index tracker start date/time is reset on index deregistration/registration.
TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) {
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
auto statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
ASSERT_EQUALS(statsMap->at("foo")->trackerStartTime, getClockSource()->now());
@@ -145,7 +151,7 @@ TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) {
// Increment clock source so that a new index registration has different start time.
getClockSource()->advance(Milliseconds(1));
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
ASSERT_EQUALS(statsMap->at("foo")->trackerStartTime, getClockSource()->now());
@@ -154,7 +160,7 @@ TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) {
// Test that the fetched stats map retains an index after the entry is concurrently removed.
TEST_F(CollectionIndexUsageTrackerTest, UsageStatsMapRemainsValidAfterIndexErasure) {
// Set up an index in the tracker.
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
getTracker()->recordIndexAccess("foo");
auto statsMap = getTracker()->getUsageStats();
@@ -177,7 +183,7 @@ TEST_F(CollectionIndexUsageTrackerTest, UsageStatsMapRemainsValidAfterIndexErasu
// recreates the index.
TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfterErasure) {
// Set up an index in the tracker.
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
auto statsMap = getTracker()->getUsageStats();
ASSERT(statsMap->find("foo") != statsMap->end());
@@ -189,7 +195,7 @@ TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfter
auto staleStatsMap = getTracker()->getUsageStats();
getTracker()->unregisterIndex("foo");
- getTracker()->registerIndex("foo", BSON("foo" << 1));
+ getTracker()->registerIndex("foo", BSON("foo" << 1), {});
getTracker()->recordIndexAccess("foo");
getTracker()->recordIndexAccess("foo");
statsMap = getTracker()->getUsageStats();
@@ -200,5 +206,123 @@ TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfter
ASSERT_EQUALS(1, staleStatsMap->at("foo")->accesses.loadRelaxed());
}
+namespace {
+int getFeatureUseCount(GlobalIndexUsageTracker* globalIndexUsage, std::string featureSearch) {
+ int count = 0;
+ globalIndexUsage->forEachFeature([&](auto feature, auto& stats) {
+ if (featureSearch == feature) {
+ count += stats.count.load();
+ }
+ });
+ return count;
+}
+
+int getFeatureAccessCount(GlobalIndexUsageTracker* globalIndexUsage, std::string featureSearch) {
+ int accesses = 0;
+ globalIndexUsage->forEachFeature([&](auto feature, auto& stats) {
+ if (featureSearch == feature) {
+ accesses += stats.accesses.load();
+ }
+ });
+ return accesses;
+}
+} // namespace
+
+TEST_F(CollectionIndexUsageTrackerTest, GlobalFeatureUsageBasic) {
+ auto idSpec = BSON("key" << BSON("_id" << 1) << "unique" << true << "v" << 2);
+ auto idDesc = IndexDescriptor("", idSpec);
+ getTracker()->registerIndex("_id_", idSpec, IndexFeatures::make(&idDesc, false /* internal */));
+ getTracker()->recordIndexAccess("_id_");
+
+ ASSERT_EQ(1, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+
+ auto spec = BSON("key" << BSON("foo" << 1) << "unique" << true << "sparse" << true << "v" << 2);
+ auto desc = IndexDescriptor("", spec);
+ getTracker()->registerIndex("foo", spec, IndexFeatures::make(&desc, false /* internal */));
+ getTracker()->recordIndexAccess("foo");
+
+ ASSERT_EQ(2, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+
+ // Register an internal index and expect nothing to change.
+ getTracker()->registerIndex("foo2", spec, IndexFeatures::make(&desc, true /* internal */));
+
+ ASSERT_EQ(2, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+
+ getTracker()->unregisterIndex("foo2");
+ ASSERT_EQ(2, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+
+ getTracker()->unregisterIndex("foo");
+ ASSERT_EQ(1, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+
+ getTracker()->unregisterIndex("_id_");
+ ASSERT_EQ(0, getGlobalIndexUsage()->getCount());
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique"));
+
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse"));
+ ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique"));
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/global_index_usage_tracker.cpp b/src/mongo/db/global_index_usage_tracker.cpp
new file mode 100644
index 00000000000..85be3b747bc
--- /dev/null
+++ b/src/mongo/db/global_index_usage_tracker.cpp
@@ -0,0 +1,223 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/global_index_usage_tracker.h"
+
+#include "mongo/db/commands/server_status.h"
+#include "mongo/db/index/index_descriptor.h"
+#include "mongo/db/service_context.h"
+
+namespace mongo {
+namespace {
+// List of index feature description strings.
+static const std::string k2d = "2d";
+static const std::string k2dSphere = "2dsphere";
+static const std::string kCollation = "collation";
+static const std::string kCompound = "compound";
+static const std::string kHashed = "hashed";
+static const std::string kId = "id";
+static const std::string kNormal = "normal";
+static const std::string kPartial = "partial";
+static const std::string kSingle = "single";
+static const std::string kSparse = "sparse";
+static const std::string kText = "text";
+static const std::string kTTL = "ttl";
+static const std::string kUnique = "unique";
+static const std::string kWildcard = "wildcard";
+
+std::map<std::string, IndexFeatureStats> makeFeatureMap() {
+ std::map<std::string, IndexFeatureStats> map;
+ map[k2d];
+ map[k2dSphere];
+ map[kCollation];
+ map[kCompound];
+ map[kHashed];
+ map[kId];
+ map[kNormal];
+ map[kPartial];
+ map[kSingle];
+ map[kSparse];
+ map[kText];
+ map[kTTL];
+ map[kUnique];
+ map[kWildcard];
+ return map;
+};
+
+const auto getGlobalIndexUsageTracker =
+ ServiceContext::declareDecoration<GlobalIndexUsageTracker>();
+} // namespace
+
+
+IndexFeatures IndexFeatures::make(const IndexDescriptor* desc, bool internal) {
+ IndexFeatures features;
+
+ int count = 0;
+ for (auto it = desc->keyPattern().begin(); it != desc->keyPattern().end(); it++) {
+ count++;
+ }
+ tassert(6325400, "index key pattern must have at least one element", count);
+
+ auto indexType = IndexNames::nameToType(desc->getAccessMethodName());
+ features.collation = !desc->collation().isEmpty();
+ // Text indexes add an extra field internally, but we don't want to expose that detail.
+ features.compound = (indexType == IndexType::INDEX_TEXT) ? count > 2 : count > 1;
+ features.id = desc->isIdIndex();
+ features.internal = internal;
+ features.partial = desc->isPartial();
+ features.sparse = desc->isSparse();
+ features.ttl = desc->infoObj().hasField(IndexDescriptor::kExpireAfterSecondsFieldName);
+ features.type = indexType;
+ features.unique = desc->unique();
+ return features;
+}
+
+GlobalIndexUsageTracker::GlobalIndexUsageTracker() : _indexFeatureToStats(makeFeatureMap()) {}
+
+GlobalIndexUsageTracker* GlobalIndexUsageTracker::get(ServiceContext* svcCtx) {
+ return &getGlobalIndexUsageTracker(svcCtx);
+}
+
+void GlobalIndexUsageTracker::onAccess(const IndexFeatures& features) const {
+ if (!features.internal) {
+ _updateStatsForEachFeature(features, [](auto stats) { stats->accesses.fetchAndAdd(1); });
+ }
+}
+
+void GlobalIndexUsageTracker::onRegister(const IndexFeatures& features) const {
+ if (!features.internal) {
+ _updateStatsForEachFeature(features, [](auto stats) { stats->count.fetchAndAdd(1); });
+ _count.fetchAndAdd(1);
+ }
+}
+
+void GlobalIndexUsageTracker::onUnregister(const IndexFeatures& features) const {
+ if (!features.internal) {
+ _updateStatsForEachFeature(features, [](auto stats) { stats->count.fetchAndAdd(-1); });
+ _count.fetchAndAdd(-1);
+ }
+}
+
+void GlobalIndexUsageTracker::_updateStatsForEachFeature(const IndexFeatures& features,
+ UpdateFn&& update) const {
+ // Aggregate _id indexes separately so they do not get included with the other features.
+ if (features.id) {
+ update(&_indexFeatureToStats.at(kId));
+ return;
+ }
+
+ switch (features.type) {
+ case INDEX_BTREE:
+ update(&_indexFeatureToStats.at(kNormal));
+ break;
+ case INDEX_2D:
+ update(&_indexFeatureToStats.at(k2d));
+ break;
+ case INDEX_HAYSTACK:
+ // MongoDB does not support haystack indexes. This enum only exists to reject creating
+ // them.
+ break;
+ case INDEX_2DSPHERE:
+ update(&_indexFeatureToStats.at(k2dSphere));
+ break;
+ case INDEX_TEXT:
+ update(&_indexFeatureToStats.at(kText));
+ break;
+ case INDEX_HASHED:
+ update(&_indexFeatureToStats.at(kHashed));
+ break;
+ case INDEX_WILDCARD:
+ update(&_indexFeatureToStats.at(kWildcard));
+ break;
+ default:
+ MONGO_UNREACHABLE;
+ }
+
+ if (features.collation) {
+ update(&_indexFeatureToStats.at(kCollation));
+ }
+ if (features.compound) {
+ update(&_indexFeatureToStats.at(kCompound));
+ } else {
+ update(&_indexFeatureToStats.at(kSingle));
+ }
+ if (features.partial) {
+ update(&_indexFeatureToStats.at(kPartial));
+ }
+ if (features.sparse) {
+ update(&_indexFeatureToStats.at(kSparse));
+ }
+ if (features.ttl) {
+ update(&_indexFeatureToStats.at(kTTL));
+ }
+ if (features.unique) {
+ update(&_indexFeatureToStats.at(kUnique));
+ }
+}
+
+void GlobalIndexUsageTracker::forEachFeature(OnFeatureFn&& onFeature) const {
+ for (auto& [key, value] : _indexFeatureToStats) {
+ onFeature(key, value);
+ }
+}
+
+long long GlobalIndexUsageTracker::getCount() const {
+ return _count.load();
+}
+
+class IndexStatsSSS : public ServerStatusSection {
+public:
+ IndexStatsSSS() : ServerStatusSection("indexStats") {}
+
+ ~IndexStatsSSS() override = default;
+
+ bool includeByDefault() const override {
+ return true;
+ }
+
+ BSONObj generateSection(OperationContext* opCtx,
+ const BSONElement& configElement) const override {
+ auto globalFeatures = GlobalIndexUsageTracker::get(opCtx->getServiceContext());
+
+ BSONObjBuilder builder;
+ builder.append("count", globalFeatures->getCount());
+
+ BSONObjBuilder featuresBuilder = builder.subobjStart("features");
+ globalFeatures->forEachFeature(
+ [&featuresBuilder](const std::string& feature, const IndexFeatureStats& stats) {
+ BSONObjBuilder featureBuilder = featuresBuilder.subobjStart(feature);
+ featureBuilder.append("count", stats.count.load());
+ featureBuilder.append("accesses", stats.accesses.load());
+ featureBuilder.done();
+ });
+ featuresBuilder.done();
+ return builder.obj();
+ }
+} indexStatsSSS;
+} // namespace mongo
diff --git a/src/mongo/db/global_index_usage_tracker.h b/src/mongo/db/global_index_usage_tracker.h
new file mode 100644
index 00000000000..c4711667787
--- /dev/null
+++ b/src/mongo/db/global_index_usage_tracker.h
@@ -0,0 +1,132 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <map>
+
+#include "mongo/db/index_names.h"
+#include "mongo/platform/atomic_word.h"
+
+namespace mongo {
+class IndexDescriptor;
+class ServiceContext;
+
+/**
+ * IndexFeatures describes an anonymized set of features about a single index. For example, an index
+ * can be both compound and unique, and this set of flags would be used to track that information so
+ * that we can provide aggregated details in the GlobalIndexUsageTracker.
+ */
+struct IndexFeatures {
+ /**
+ * Create an IndexFeatures structure. If 'internal' is true, the statistics for this index and
+ * its features should not be tracked and aggregated by the GlobalIndexUsageTracker.
+ */
+ static IndexFeatures make(const IndexDescriptor* desc, bool internal);
+
+ IndexType type;
+ bool collation = false;
+ bool compound = false;
+ bool id = false;
+ bool internal = false;
+ bool partial = false;
+ bool regular = false;
+ bool sparse = false;
+ bool ttl = false;
+ bool unique = false;
+};
+
+/**
+ * IndexFeatureStats holds statistics about a specific index feature. Its data members are mutable
+ * atomics to allow itself to be used in a const map safely.
+ */
+struct IndexFeatureStats {
+ // Number of indexes that have this feature.
+ mutable AtomicWord<long long> count{0};
+ // Number of operations that have used indexes with this feature.
+ mutable AtomicWord<long long> accesses{0};
+};
+
+/**
+ * GlobalIndexUsageTracker aggregates usage metrics about features used by indexes. Ignores indexes
+ * on internal databases.
+ */
+class GlobalIndexUsageTracker {
+public:
+ static GlobalIndexUsageTracker* get(ServiceContext* svcCtx);
+
+ GlobalIndexUsageTracker();
+
+ /**
+ * Updates counters for features used by an index when the index has been accessed.
+ */
+ void onAccess(const IndexFeatures& features) const;
+
+ /**
+ * Updates counters for indexes using certain features when the index has been created.
+ */
+ void onRegister(const IndexFeatures& features) const;
+
+ /**
+ * Updates counters for indexes using certain features when the index has been removed.
+ */
+ void onUnregister(const IndexFeatures& features) const;
+
+ /**
+ * Iterates through each feature being tracked with a call back to OnFeatureFn, which provides
+ * the string descriptor of the feature and its stats.
+ */
+ using OnFeatureFn =
+ std::function<void(const std::string& feature, const IndexFeatureStats& stats)>;
+ void forEachFeature(OnFeatureFn&& onFeature) const;
+
+ /**
+ * Returns the total number of indexes being tracked.
+ */
+ long long getCount() const;
+
+private:
+ /**
+ * Internal helper to update the global stats for each index feature in 'features'. Call back
+ * into UpdateFn which is responsible for update the relevant statistic for each of the features
+ * in use.
+ */
+ using UpdateFn = std::function<void(const IndexFeatureStats* stats)>;
+ void _updateStatsForEachFeature(const IndexFeatures& features, UpdateFn&& onFeature) const;
+
+ // This maps index feature description strings (e.g. 'compound', 'unique') to the actual global
+ // metrics counters for those features. It is intentionally ordered to have consistent
+ // alphabetical ordering when displayed. This map is const because we know all of its entries
+ // ahead of time and we can avoid the extra cost of additional concurrency control.
+ const std::map<std::string, IndexFeatureStats> _indexFeatureToStats;
+
+ // Total number of indexes being tracked.
+ mutable AtomicWord<long long> _count;
+};
+} // namespace mongo
diff --git a/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp b/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp
index 504be0ef934..59ae9bd1c1d 100644
--- a/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp
+++ b/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp
@@ -51,6 +51,7 @@ CollectionIndexUsageTracker& CollectionIndexUsageTrackerDecoration::get(
}
CollectionIndexUsageTrackerDecoration::CollectionIndexUsageTrackerDecoration()
- : _indexUsageTracker(getGlobalServiceContext()->getPreciseClockSource()) {}
+ : _indexUsageTracker(GlobalIndexUsageTracker::get(getGlobalServiceContext()),
+ getGlobalServiceContext()->getPreciseClockSource()) {}
} // namespace mongo
diff --git a/src/mongo/db/query/collection_query_info.cpp b/src/mongo/db/query/collection_query_info.cpp
index e66c23bad92..1a87201bae3 100644
--- a/src/mongo/db/query/collection_query_info.cpp
+++ b/src/mongo/db/query/collection_query_info.cpp
@@ -234,7 +234,9 @@ void CollectionQueryInfo::init(OperationContext* opCtx, const CollectionPtr& col
while (ii->more()) {
const IndexDescriptor* desc = ii->next()->descriptor();
CollectionIndexUsageTrackerDecoration::get(coll->getSharedDecorations())
- .registerIndex(desc->indexName(), desc->keyPattern());
+ .registerIndex(desc->indexName(),
+ desc->keyPattern(),
+ IndexFeatures::make(desc, coll->ns().isOnInternalDb()));
}
rebuildIndexData(opCtx, coll);