Skip to content

Commit b619eef

Browse files
PMM-9312 default collectors (#396)
* release-0.30.0 * PMM-9312 Added new filters for discovering mode * Added flags for collstats & indexstats * Update main.go Co-authored-by: Nurlan Moldomurov <[email protected]> * Changes for CR * Updates for CR * Ran format * Fixed top collector flag * Updated filters * PMM-9312 Fix MongoFilter (#399) Co-authored-by: Nurlan Moldomurov <[email protected]>
1 parent 88c186c commit b619eef

14 files changed

+280
-141
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,4 @@ test-cluster: env ## Starts MongoDB test cluster. Use env var TEST_MON
110110
docker-compose up -d
111111

112112
test-cluster-clean: env ## Stops MongoDB test cluster.
113-
docker-compose down
113+
docker-compose down --remove-orphans

exporter/collstats_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (d *collstatsCollector) Collect(ch chan<- prometheus.Metric) {
4444
collections := d.collections
4545

4646
if d.discoveringMode {
47-
namespaces, err := listAllCollections(d.ctx, d.client, d.collections)
47+
namespaces, err := listAllCollections(d.ctx, d.client, d.collections, systemDBs)
4848
if err != nil {
4949
d.logger.Errorf("cannot auto discover databases and collections")
5050

exporter/common.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package exporter
22

33
import (
44
"context"
5+
"sort"
6+
"strings"
57

68
"github.com/AlekSi/pointer"
79
"github.com/pkg/errors"
@@ -13,81 +15,133 @@ import (
1315

1416
var systemDBs = []string{"admin", "config", "local"} //nolint:gochecknoglobals
1517

16-
func listCollections(ctx context.Context, client *mongo.Client, collections []string, database string) ([]string, error) {
18+
func listCollections(ctx context.Context, client *mongo.Client, database string, filterInNamespaces []string) ([]string, error) {
1719
filter := bson.D{} // Default=empty -> list all collections
1820

1921
// if there is a filter with the list of collections we want, create a filter like
2022
// $or: {
2123
// {"$regex": "collection1"},
2224
// {"$regex": "collection2"},
2325
// }
24-
if len(collections) > 0 {
26+
if len(filterInNamespaces) > 0 {
2527
matchExpressions := []bson.D{}
2628

27-
for _, collection := range collections {
28-
matchExpressions = append(matchExpressions,
29-
bson.D{{Key: "name", Value: primitive.Regex{Pattern: collection, Options: "i"}}})
29+
for _, namespace := range filterInNamespaces {
30+
parts := strings.Split(namespace, ".") // db.collection.name.with.dots
31+
if len(parts) > 1 {
32+
// The part before the first dot is the database name.
33+
// The rest is the collection name and it can have dots. We need to rebuild it.
34+
collection := strings.Join(parts[1:], ".")
35+
matchExpressions = append(matchExpressions,
36+
bson.D{{Key: "name", Value: primitive.Regex{Pattern: collection, Options: "i"}}})
37+
}
3038
}
3139

32-
filter = bson.D{{Key: "$or", Value: matchExpressions}}
40+
if len(matchExpressions) > 0 {
41+
filter = bson.D{{Key: "$or", Value: matchExpressions}}
42+
}
3343
}
3444

35-
databases, err := client.Database(database).ListCollectionNames(ctx, filter)
45+
collections, err := client.Database(database).ListCollectionNames(ctx, filter)
3646
if err != nil {
3747
return nil, errors.Wrap(err, "cannot get the list of collections for discovery")
3848
}
3949

40-
return databases, nil
50+
return collections, nil
4151
}
4252

43-
func databases(ctx context.Context, client *mongo.Client, exclude []string) ([]string, error) {
53+
// databases returns the list of databases matching the filters.
54+
// - filterInNamespaces: Include only the database names matching the any of the regular expressions in this list.
55+
// Case will be ignored because the function will automatically add the ignore case
56+
// flag to the regular expression.
57+
// - exclude: List of databases to be excluded. Useful to ignore system databases.
58+
func databases(ctx context.Context, client *mongo.Client, filterInNamespaces []string, exclude []string) ([]string, error) {
4459
opts := &options.ListDatabasesOptions{NameOnly: pointer.ToBool(true), AuthorizedDatabases: pointer.ToBool(true)}
60+
61+
filter := bson.D{}
62+
63+
if excludeFilter := makeExcludeFilter(exclude); excludeFilter != nil {
64+
filter = append(filter, *excludeFilter)
65+
}
66+
67+
if namespacesFilter := makeDBsFilter(filterInNamespaces); namespacesFilter != nil {
68+
filter = append(filter, *namespacesFilter)
69+
}
70+
71+
dbNames, err := client.ListDatabaseNames(ctx, filter, opts)
72+
if err != nil {
73+
return nil, errors.Wrap(err, "cannot get the database names list")
74+
}
75+
76+
return dbNames, nil
77+
}
78+
79+
func makeExcludeFilter(exclude []string) *primitive.E {
4580
filterExpressions := []bson.D{}
4681
for _, dbname := range exclude {
4782
filterExpressions = append(filterExpressions,
4883
bson.D{{Key: "name", Value: bson.D{{Key: "$ne", Value: dbname}}}},
4984
)
5085
}
5186

52-
filter := bson.D{{Key: "$and", Value: filterExpressions}}
87+
if len(filterExpressions) == 0 {
88+
return nil
89+
}
5390

54-
dbNames, err := client.ListDatabaseNames(ctx, filter, opts)
55-
if err != nil {
56-
return nil, errors.Wrap(err, "cannot get the database names list")
91+
return &primitive.E{Key: "$and", Value: filterExpressions}
92+
}
93+
94+
// makeDBsFilter creates a filter to list all databases or only the ones in the specified
95+
// namespaces list. Namespaces have the form of <db>.<collection> and the collection name
96+
// can have a dot. Example: db1.collection.one -> db: db1, collection: collection.one
97+
// db1, db2.col2, db3.col.one will produce [db1, db2, db3].
98+
func makeDBsFilter(filterInNamespaces []string) *primitive.E {
99+
dbs := []string{}
100+
if len(dbs) == 0 {
101+
return nil
57102
}
58103

59-
return dbNames, nil
104+
for _, namespace := range filterInNamespaces {
105+
parts := strings.Split(namespace, ".")
106+
dbs = append(dbs, parts[0])
107+
}
108+
109+
return &primitive.E{Key: "name", Value: primitive.E{Key: "$in", Value: dbs}}
60110
}
61111

62-
func listAllCollections(ctx context.Context, client *mongo.Client, filter []string) (map[string][]string, error) {
112+
func listAllCollections(ctx context.Context, client *mongo.Client, filterInNamespaces []string, excludeDBs []string) (map[string][]string, error) {
63113
namespaces := make(map[string][]string)
64-
// exclude system databases
65-
dbnames, err := databases(ctx, client, systemDBs)
66-
if err != nil {
67-
return nil, errors.Wrap(err, "cannot get the list of all collections in the server")
68-
}
69114

70-
for _, dbname := range dbnames {
71-
colls, err := listCollections(ctx, client, filter, dbname)
115+
for _, namespace := range filterInNamespaces {
116+
parts := strings.Split(namespace, ".")
117+
dbname := parts[0]
118+
119+
colls, err := listCollections(ctx, client, dbname, []string{namespace})
72120
if err != nil {
73121
return nil, errors.Wrapf(err, "cannot list the collections for %q", dbname)
74122
}
75-
namespaces[dbname] = colls
123+
124+
if _, ok := namespaces[dbname]; !ok {
125+
namespaces[dbname] = colls
126+
} else {
127+
namespaces[dbname] = append(namespaces[dbname], colls...)
128+
}
129+
sort.Strings(namespaces[dbname]) // make it testeable.
76130
}
77131

78132
return namespaces, nil
79133
}
80134

81-
func allCollectionsCount(ctx context.Context, client *mongo.Client, filter []string) (int, error) {
82-
databases, err := databases(ctx, client, systemDBs)
135+
func nonSystemCollectionsCount(ctx context.Context, client *mongo.Client, includeNamespaces []string, filterInCollections []string) (int, error) {
136+
databases, err := databases(ctx, client, includeNamespaces, systemDBs)
83137
if err != nil {
84138
return 0, errors.Wrap(err, "cannot retrieve the collection names for count collections")
85139
}
86140

87141
var count int
88142

89143
for _, dbname := range databases {
90-
colls, err := listCollections(ctx, client, filter, dbname)
144+
colls, err := listCollections(ctx, client, dbname, filterInCollections)
91145
if err != nil {
92146
return 0, errors.Wrap(err, "cannot get collections count")
93147
}

exporter/common_test.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,54 @@ func TestListCollections(t *testing.T) {
1919

2020
client := tu.DefaultTestClient(ctx, t)
2121

22-
databases := []string{"testdb01", "testdb02"}
23-
collections := []string{"col01", "col02", "colxx", "colyy"}
22+
inDBs := []string{"testdb01", "testdb02"}
23+
inColls := []string{"col01", "col02", "colxx", "colyy"}
2424

2525
defer func() {
26-
for _, dbname := range databases {
26+
for _, dbname := range inDBs {
2727
client.Database(dbname).Drop(ctx) //nolint:errcheck
2828
}
2929
}()
3030

31-
for _, dbname := range databases {
32-
for _, coll := range collections {
31+
for _, dbname := range inDBs {
32+
for _, coll := range inColls {
3333
for j := 0; j < 10; j++ {
3434
_, err := client.Database(dbname).Collection(coll).InsertOne(ctx, bson.M{"f1": j, "f2": "2"})
3535
assert.NoError(t, err)
3636
}
3737
}
3838
}
3939

40-
want := []string{"col01", "col02", "colxx"}
41-
collections, err := listCollections(ctx, client, []string{"col0", "colx"}, databases[0])
42-
sort.Strings(collections)
40+
want := []string{"admin", "config", "local", "testdb01", "testdb02"}
41+
allDBs, err := databases(ctx, client, nil, nil)
42+
assert.NoError(t, err)
43+
assert.Equal(t, want, allDBs)
44+
45+
want = []string{"col01", "col02", "colxx"}
46+
inNameSpaces := []string{inDBs[0] + ".col0", inDBs[0] + ".colx"}
47+
colls, err := listCollections(ctx, client, inDBs[0], inNameSpaces)
48+
sort.Strings(colls)
49+
50+
assert.NoError(t, err)
51+
assert.Equal(t, want, colls)
52+
53+
// Advanced filtering test
54+
wantNS := map[string][]string{
55+
"testdb01": {"col01", "col02", "colxx", "colyy"},
56+
"testdb02": {"col01", "col02"},
57+
}
4358

59+
// List all collections in testdb01 (inDBs[0]) but only col01 and col02 from testdb02.
60+
filterInNameSpaces := []string{inDBs[0], inDBs[1] + ".col01", inDBs[1] + ".col02"}
61+
namespaces, err := listAllCollections(ctx, client, filterInNameSpaces, systemDBs)
4462
assert.NoError(t, err)
45-
assert.Equal(t, want, collections)
63+
assert.Equal(t, wantNS, namespaces)
4664

47-
count, err := allCollectionsCount(ctx, client, nil)
65+
count, err := nonSystemCollectionsCount(ctx, client, nil, nil)
4866
assert.NoError(t, err)
49-
assert.True(t, count > 8)
67+
assert.True(t, count == 8)
5068

51-
count, err = allCollectionsCount(ctx, client, []string{"col0", "colx"})
69+
count, err = nonSystemCollectionsCount(ctx, client, nil, []string{inDBs[0] + ".col0", inDBs[0] + ".colx"})
5270
assert.NoError(t, err)
5371
assert.Equal(t, 6, count)
5472
}

exporter/dbstats_collector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ func (d *dbstatsCollector) Collect(ch chan<- prometheus.Metric) {
4242
dbNames, err := d.client.ListDatabaseNames(d.ctx, bson.M{})
4343
if err != nil {
4444
d.logger.Errorf("Failed to get database names: %s", err)
45+
4546
return
4647
}
48+
4749
d.logger.Debugf("getting stats for databases: %v", dbNames)
4850
for _, db := range dbNames {
4951
var dbStats bson.M
@@ -52,6 +54,7 @@ func (d *dbstatsCollector) Collect(ch chan<- prometheus.Metric) {
5254
err := r.Decode(&dbStats)
5355
if err != nil {
5456
d.logger.Errorf("Failed to get $dbstats for database %s: %s", db, err)
57+
5558
continue
5659
}
5760

exporter/diagnostic_data_collector_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ func addTestData(ctx context.Context, client *mongo.Client, count int) error {
164164
}
165165

166166
if err = mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
167+
ctx, cancel := context.WithCancel(ctx)
168+
defer cancel()
169+
167170
for i := 0; i < count; i++ {
168171
dbName := fmt.Sprintf("testdb_%06d", i)
169172
doc := bson.D{{Key: "field1", Value: "value 1"}}
@@ -173,7 +176,7 @@ func addTestData(ctx context.Context, client *mongo.Client, count int) error {
173176
}
174177
}
175178

176-
if err = session.CommitTransaction(sc); err != nil {
179+
if err = session.CommitTransaction(ctx); err != nil {
177180
return errors.Wrap(err, "cannot commit add test data transaction")
178181
}
179182

0 commit comments

Comments
 (0)