Skip to content

Commit c9afe29

Browse files
committed
PMMM-14431 Reserved Names for indexes.
1 parent 985c115 commit c9afe29

12 files changed

+164
-20
lines changed

exporter/collstats_collector.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ func (d *collstatsCollector) collect(ch chan<- prometheus.Metric) {
8585
}
8686
}
8787

88+
reservedNames, err := GetAllIndexesForCollections(d.ctx, client, collections)
89+
if err != nil {
90+
logger.Error("cannot get all indexes for collections", "error", err.Error())
91+
return
92+
}
93+
8894
for _, dbCollection := range collections {
8995
parts := strings.Split(dbCollection, ".")
9096
if len(parts) < 2 { //nolint:gomnd
@@ -151,7 +157,7 @@ func (d *collstatsCollector) collect(ch chan<- prometheus.Metric) {
151157
labels["shard"] = shard
152158
}
153159

154-
for _, metric := range makeMetrics(prefix, metrics, labels, d.compatibleMode) {
160+
for _, metric := range makeMetrics(reservedNames, prefix, metrics, labels, d.compatibleMode) {
155161
ch <- metric
156162
}
157163
}

exporter/collstats_collector_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import (
2525
"github.com/prometheus/client_golang/prometheus/testutil"
2626
"github.com/prometheus/common/promslog"
2727
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/require"
2829
"go.mongodb.org/mongo-driver/bson"
30+
"go.mongodb.org/mongo-driver/mongo"
31+
"go.mongodb.org/mongo-driver/mongo/options"
2932

3033
"github.com/percona/mongodb_exporter/internal/tu"
3134
)
@@ -93,3 +96,64 @@ mongodb_collstats_storageStats_capped{collection="testcol_02",database="testdb"}
9396
err := testutil.CollectAndCompare(c, expected, filter...)
9497
assert.NoError(t, err)
9598
}
99+
100+
func TestCollStatsCollectorAccountIndexes(t *testing.T) {
101+
t.Parallel()
102+
ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)
103+
defer cancel()
104+
105+
client := tu.DefaultTestClient(ctx, t)
106+
107+
database := client.Database("testdb")
108+
database.Drop(ctx) //nolint
109+
110+
defer func() {
111+
err := database.Drop(ctx)
112+
require.NoError(t, err)
113+
}()
114+
115+
collName := "test_collection_account"
116+
coll := database.Collection(collName)
117+
118+
// Insert some data
119+
_, err := coll.InsertOne(ctx, bson.M{"account_id": 1, "count": 10})
120+
require.NoError(t, err)
121+
_, err = coll.InsertOne(ctx, bson.M{"account_id": 2, "count": 20})
122+
require.NoError(t, err)
123+
124+
// Create indexes
125+
indexModel := mongo.IndexModel{
126+
Keys: bson.D{{Key: "account_id", Value: 1}},
127+
Options: options.Index().SetName("test_index_account"),
128+
}
129+
_, err = coll.Indexes().CreateOne(ctx, indexModel)
130+
require.NoError(t, err)
131+
132+
indexModel = mongo.IndexModel{
133+
Keys: bson.D{{Key: "count", Value: 1}},
134+
Options: options.Index().SetName("test_index_count"),
135+
}
136+
_, err = coll.Indexes().CreateOne(ctx, indexModel)
137+
require.NoError(t, err)
138+
139+
ti := labelsGetterMock{}
140+
141+
collection := []string{"testdb.test_collection_account"}
142+
logger := promslog.New(&promslog.Config{})
143+
c := newCollectionStatsCollector(ctx, client, logger, false, ti, collection, false)
144+
145+
// Expected metrics (simplified, adjust values as needed)
146+
expected := strings.NewReader(`
147+
# HELP mongodb_collstats_storageStats_indexSizes collstats.storageStats.indexSizes
148+
# TYPE mongodb_collstats_storageStats_indexSizes untyped
149+
mongodb_collstats_storageStats_indexSizes{collection="test_collection_account",database="testdb",index_name="_id_"} 4096
150+
mongodb_collstats_storageStats_indexSizes{collection="test_collection_account",database="testdb",index_name="test_index_account"} 20480
151+
mongodb_collstats_storageStats_indexSizes{collection="test_collection_account",database="testdb",index_name="test_index_count"} 20480
152+
`)
153+
154+
filter := []string{
155+
"mongodb_collstats_storageStats_indexSizes",
156+
}
157+
err = testutil.CollectAndCompare(c, expected, filter...)
158+
require.NoError(t, err)
159+
}

exporter/dbstats_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func (d *dbstatsCollector) collect(ch chan<- prometheus.Metric) {
100100
// to differentiate metrics between different databases.
101101
labels["database"] = db
102102

103-
newMetrics := makeMetrics(prefix, dbStats, labels, d.compatibleMode)
103+
newMetrics := makeMetrics(nil, prefix, dbStats, labels, d.compatibleMode)
104104
for _, metric := range newMetrics {
105105
ch <- metric
106106
}

exporter/diagnostic_data_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (d *diagnosticDataCollector) collect(ch chan<- prometheus.Metric) {
126126
m = b
127127
}
128128

129-
metrics = makeMetrics("", m, d.topologyInfo.baseLabels(), d.compatibleMode)
129+
metrics = makeMetrics(nil, "", m, d.topologyInfo.baseLabels(), d.compatibleMode)
130130
metrics = append(metrics, locksMetrics(logger, m)...)
131131

132132
securityMetric, err := d.getSecurityMetricFromLineOptions(client)

exporter/indexstats_collector.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ func (d *indexstatsCollector) collect(ch chan<- prometheus.Metric) {
8585
}
8686
}
8787

88+
reservedNames, err := GetAllIndexesForCollections(d.ctx, client, collections)
89+
if err != nil {
90+
logger.Error("cannot get all indexes for collections", "error", err.Error())
91+
return
92+
}
93+
8894
for _, dbCollection := range collections {
8995
parts := strings.Split(dbCollection, ".")
9096
if len(parts) < 2 { //nolint:gomnd
@@ -137,7 +143,7 @@ func (d *indexstatsCollector) collect(ch chan<- prometheus.Metric) {
137143
labels["key_name"] = indexName
138144

139145
metrics := sanitizeMetrics(metric)
140-
for _, metric := range makeMetrics(prefix, metrics, labels, false) {
146+
for _, metric := range makeMetrics(reservedNames, prefix, metrics, labels, false) {
141147
ch <- metric
142148
}
143149
}

exporter/metrics.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package exporter
1717

1818
import (
19+
"context"
1920
"regexp"
21+
"slices"
2022
"strings"
2123
"sync"
2224
"time"
@@ -25,6 +27,7 @@ import (
2527
"github.com/prometheus/client_golang/prometheus"
2628
"go.mongodb.org/mongo-driver/bson"
2729
"go.mongodb.org/mongo-driver/bson/primitive"
30+
"go.mongodb.org/mongo-driver/mongo"
2831
)
2932

3033
const (
@@ -206,7 +209,7 @@ func nameAndLabel(prefix, name string) (string, string) {
206209

207210
// makeRawMetric creates a Prometheus metric based on the parameters we collected by
208211
// traversing the MongoDB structures returned by the collector functions.
209-
func makeRawMetric(prefix, name string, value interface{}, labels map[string]string) (*rawMetric, error) {
212+
func makeRawMetric(reservedNames []string, prefix, name string, value interface{}, labels map[string]string) (*rawMetric, error) {
210213
f, err := asFloat64(value)
211214
if err != nil {
212215
return nil, err
@@ -220,8 +223,10 @@ func makeRawMetric(prefix, name string, value interface{}, labels map[string]str
220223
fqName, label := nameAndLabel(prefix, name)
221224

222225
metricType := prometheus.UntypedValue
223-
if strings.HasSuffix(strings.ToLower(name), "count") {
224-
metricType = prometheus.CounterValue
226+
if !slices.Contains(reservedNames, name) {
227+
if strings.HasSuffix(strings.ToLower(name), "count") {
228+
metricType = prometheus.CounterValue
229+
}
225230
}
226231

227232
rm := &rawMetric{
@@ -301,7 +306,41 @@ func metricHelp(prefix, name string) string {
301306
return name
302307
}
303308

304-
func makeMetrics(prefix string, m bson.M, labels map[string]string, compatibleMode bool) []prometheus.Metric {
309+
// GetAllIndexesForCollections returns all index names for the given list of "db.collection" strings.
310+
func GetAllIndexesForCollections(ctx context.Context, client *mongo.Client, collections []string) ([]string, error) {
311+
var indexNames []string
312+
313+
for _, dbCollection := range collections {
314+
parts := strings.SplitN(dbCollection, ".", 2)
315+
if len(parts) != 2 {
316+
continue // skip invalid format
317+
}
318+
dbName := parts[0]
319+
collName := parts[1]
320+
321+
coll := client.Database(dbName).Collection(collName)
322+
cursor, err := coll.Indexes().List(ctx)
323+
if err != nil {
324+
// skip collections where indexes cannot be listed (e.g., views, system collections)
325+
continue
326+
}
327+
defer cursor.Close(ctx)
328+
329+
for cursor.Next(ctx) {
330+
var idxDoc struct {
331+
Name string `bson:"name"`
332+
}
333+
if err := cursor.Decode(&idxDoc); err != nil {
334+
continue
335+
}
336+
indexNames = append(indexNames, idxDoc.Name)
337+
}
338+
}
339+
340+
return indexNames, nil
341+
}
342+
343+
func makeMetrics(reservedNames []string, prefix string, m bson.M, labels map[string]string, compatibleMode bool) []prometheus.Metric {
305344
var res []prometheus.Metric
306345

307346
if prefix != "" {
@@ -327,15 +366,15 @@ func makeMetrics(prefix string, m bson.M, labels map[string]string, compatibleMo
327366
}
328367
switch v := val.(type) {
329368
case bson.M:
330-
res = append(res, makeMetrics(nextPrefix, v, l, compatibleMode)...)
369+
res = append(res, makeMetrics(reservedNames, nextPrefix, v, l, compatibleMode)...)
331370
case map[string]interface{}:
332-
res = append(res, makeMetrics(nextPrefix, v, l, compatibleMode)...)
371+
res = append(res, makeMetrics(reservedNames, nextPrefix, v, l, compatibleMode)...)
333372
case primitive.A:
334-
res = append(res, processSlice(nextPrefix, v, l, compatibleMode)...)
373+
res = append(res, processSlice(reservedNames, nextPrefix, v, l, compatibleMode)...)
335374
case []interface{}:
336375
continue
337376
default:
338-
rm, err := makeRawMetric(prefix, k, v, l)
377+
rm, err := makeRawMetric(reservedNames, prefix, k, v, l)
339378
if err != nil {
340379
invalidMetric := prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(err), err)
341380
res = append(res, invalidMetric)
@@ -376,7 +415,7 @@ func makeMetrics(prefix string, m bson.M, labels map[string]string, compatibleMo
376415

377416
// Extract maps from arrays. Only some structures like replicasets have arrays of members
378417
// and each member is represented by a map[string]interface{}.
379-
func processSlice(prefix string, v []interface{}, commonLabels map[string]string, compatibleMode bool) []prometheus.Metric {
418+
func processSlice(reservedNames []string, prefix string, v []interface{}, commonLabels map[string]string, compatibleMode bool) []prometheus.Metric {
380419
metrics := make([]prometheus.Metric, 0)
381420
labels := make(map[string]string)
382421
for name, value := range commonLabels {
@@ -406,7 +445,7 @@ func processSlice(prefix string, v []interface{}, commonLabels map[string]string
406445
labels["member_idx"] = host
407446
}
408447

409-
metrics = append(metrics, makeMetrics(prefix, s, labels, compatibleMode)...)
448+
metrics = append(metrics, makeMetrics(reservedNames, prefix, s, labels, compatibleMode)...)
410449
}
411450

412451
return metrics

exporter/metrics_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func TestMakeRawMetric(t *testing.T) {
166166
}
167167
}
168168

169-
m, err := makeRawMetric(prefix, name, tc.value, nil)
169+
m, err := makeRawMetric(nil, prefix, name, tc.value, nil)
170170

171171
assert.NoError(t, err)
172172
assert.Equal(t, want, m)

exporter/profile_status_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (d *profileCollector) collect(ch chan<- prometheus.Metric) {
8888
logger.Debug("profile response from MongoDB:")
8989
debugResult(logger, primitive.M{db: m})
9090

91-
for _, metric := range makeMetrics("profile_slow_query", m, labels, d.compatibleMode) {
91+
for _, metric := range makeMetrics(nil, "profile_slow_query", m, labels, d.compatibleMode) {
9292
ch <- metric
9393
}
9494
}

exporter/replset_config_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (d *replSetGetConfigCollector) collect(ch chan<- prometheus.Metric) {
8686
logger.Debug("replSetGetConfig result:")
8787
debugResult(logger, m)
8888

89-
for _, metric := range makeMetrics("rs_cfg", m, d.topologyInfo.baseLabels(), d.compatibleMode) {
89+
for _, metric := range makeMetrics(nil, "rs_cfg", m, d.topologyInfo.baseLabels(), d.compatibleMode) {
9090
ch <- metric
9191
}
9292
}

exporter/replset_status_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (d *replSetGetStatusCollector) collect(ch chan<- prometheus.Metric) {
8181
logger.Debug("replSetGetStatus result:")
8282
debugResult(logger, m)
8383

84-
for _, metric := range makeMetrics("", m, d.topologyInfo.baseLabels(), d.compatibleMode) {
84+
for _, metric := range makeMetrics(nil, "", m, d.topologyInfo.baseLabels(), d.compatibleMode) {
8585
ch <- metric
8686
}
8787
}

0 commit comments

Comments
 (0)