-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
metricbeat/module/mongodb/collstats: Add extra collstats metrics #42171
Changes from 14 commits
eb7bbcb
9ae1258
065a342
3cef86a
b673cd9
7ef7d47
2eb5554
0ad0c69
e6006d1
6cbbccd
694b9ab
e12868d
dff33ce
ebacc6c
ac1f926
18b6e98
d133f7f
30a57f5
8efa46e
ddef8aa
a56948c
48fe367
d667587
c58807f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,11 +21,13 @@ import ( | |
"context" | ||
"errors" | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/elastic/beats/v7/metricbeat/mb" | ||
"github.com/elastic/beats/v7/metricbeat/module/mongodb" | ||
|
||
"go.mongodb.org/mongo-driver/bson" | ||
"go.mongodb.org/mongo-driver/mongo" | ||
) | ||
|
||
func init() { | ||
|
@@ -70,10 +72,6 @@ func (m *Metricset) Fetch(reporter mb.ReporterV2) error { | |
} | ||
}() | ||
|
||
if err != nil { | ||
return fmt.Errorf("could not get a list of databases: %w", err) | ||
} | ||
|
||
// This info is only stored in 'admin' database | ||
db := client.Database("admin") | ||
res := db.RunCommand(context.Background(), bson.D{bson.E{Key: "top"}}) | ||
|
@@ -95,6 +93,12 @@ func (m *Metricset) Fetch(reporter mb.ReporterV2) error { | |
return errors.New("collection 'totals' are not a map") | ||
} | ||
|
||
if err = res.Err(); err != nil { | ||
return fmt.Errorf("'top' command failed: %w", err) | ||
} | ||
|
||
wg := &sync.WaitGroup{} | ||
|
||
for group, info := range totals { | ||
if group == "note" { | ||
continue | ||
|
@@ -106,16 +110,48 @@ func (m *Metricset) Fetch(reporter mb.ReporterV2) error { | |
continue | ||
} | ||
|
||
event, err := eventMapping(group, infoMap) | ||
if err != nil { | ||
reporter.Error(fmt.Errorf("mapping of the event data filed: %w", err)) | ||
continue | ||
} | ||
|
||
reporter.Event(mb.Event{ | ||
MetricSetFields: event, | ||
}) | ||
wg.Add(1) | ||
go func(eventReporter mb.ReporterV2, mongoClient *mongo.Client, group string) { | ||
defer wg.Done() | ||
|
||
names, err := splitKey(group) | ||
if err != nil { | ||
eventReporter.Error(fmt.Errorf("splitting a collection key failed: %w", err)) | ||
return | ||
} | ||
|
||
collStats, err := fetchCollStats(mongoClient, names[0], names[1]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better naming for names[0], names[1] please |
||
if err != nil { | ||
eventReporter.Error(fmt.Errorf("fetching collStats failed: %w", err)) | ||
return | ||
} | ||
|
||
infoMap["stats"] = collStats | ||
|
||
event, err := eventMapping(group, infoMap) | ||
if err != nil { | ||
eventReporter.Error(fmt.Errorf("mapping of the event data failed: %w", err)) | ||
return | ||
} | ||
|
||
eventReporter.Event(mb.Event{ | ||
MetricSetFields: event, | ||
}) | ||
}(reporter, client, group) | ||
} | ||
|
||
wg.Wait() | ||
|
||
return nil | ||
} | ||
|
||
func fetchCollStats(client *mongo.Client, dbName, collectionName string) (map[string]interface{}, error) { | ||
db := client.Database(dbName) | ||
colStats := db.RunCommand(context.Background(), bson.M{"collStats": collectionName}) | ||
var statsRes map[string]interface{} | ||
if err := colStats.Decode(&statsRes); err != nil { | ||
return nil, fmt.Errorf("could not decode mongo response for database=%s, collection=%s: %w", dbName, collectionName, err) | ||
} | ||
|
||
return statsRes, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,10 +25,9 @@ import ( | |
) | ||
|
||
func eventMapping(key string, data mapstr.M) (mapstr.M, error) { | ||
names := strings.SplitN(key, ".", 2) | ||
|
||
if len(names) < 2 { | ||
return nil, errors.New("collection name invalid") | ||
names, err := splitKey(key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
event := mapstr.M{ | ||
|
@@ -91,6 +90,16 @@ func eventMapping(key string, data mapstr.M) (mapstr.M, error) { | |
}, | ||
"count": mustGetMapStrValue(data, "commands.count"), | ||
}, | ||
"stats": mapstr.M{ | ||
"size": mustGetMapStrValue(data, "stats.size"), | ||
"count": mustGetMapStrValue(data, "stats.count"), | ||
"avgObjSize": mustGetMapStrValue(data, "stats.avgObjSize"), | ||
"storageSize": mustGetMapStrValue(data, "stats.storageSize"), | ||
"totalIndexSize": mustGetMapStrValue(data, "stats.totalIndexSize"), | ||
"totalSize": mustGetMapStrValue(data, "stats.totalSize"), | ||
"max": mustGetMapStrValue(data, "stats.max"), | ||
"nindexes": mustGetMapStrValue(data, "stats.nindexes"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason to not add fields like -- capped? Also, see the list here: https://github.com/DataDog/integrations-core/blob/bef0a2f2971ff01176689794aaa7eb0bb0b37a9e/mongo/datadog_checks/mongo/metrics.py#L140 If we are getting them, can we please add? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are quite a lot of fileds which we get here @shmsr . |
||
}, | ||
} | ||
|
||
return event, nil | ||
|
@@ -100,3 +109,13 @@ func mustGetMapStrValue(m mapstr.M, key string) interface{} { | |
v, _ := m.GetValue(key) | ||
return v | ||
} | ||
|
||
func splitKey(key string) ([]string, error) { | ||
names := strings.SplitN(key, ".", 2) | ||
|
||
if len(names) < 2 { | ||
return nil, errors.New("collection name invalid") | ||
} | ||
|
||
return names, nil | ||
} |
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also what's the rationale behind using goroutines here? Also, even if we are keeping it can be do bounded concurrency. Dont wanna fire too many queries and burden the customer's MongoDB server.
I mean, can we add semaphore or worker pool to limit the concurrency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIP: Also, without bounded concurrency, errgroup is cleaner way to implement this: https://pkg.go.dev/golang.org/x/sync/errgroup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree to this:
For large datasets, the number of goroutines can overwhelm the system. Let's use workerpool to limit the concurrency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've replaced WaitGroup with errgroup. Btw, I set goroutins limit to 10 like was suggested in the example. Is 10 a good limit or should I change it to something else?