Skip to content

Commit fcb3850

Browse files
mikle7771Alex Tymchuk
andauthored
Collector for working with data from the system profile (#710)
* Collector for working with data from the system profile * add description * fix: tab instead of spaces --------- Co-authored-by: Alex Tymchuk <[email protected]>
1 parent 6b2e831 commit fcb3850

File tree

6 files changed

+194
-0
lines changed

6 files changed

+194
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ HELP mongodb_mongod_wiredtiger_log_bytes_total mongodb_mongod_wiredtiger_log_byt
106106
# TYPE mongodb_mongod_wiredtiger_log_bytes_total untyped
107107
mongodb_mongod_wiredtiger_log_bytes_total{type="unwritten"} 2.6208e+06
108108
```
109+
#### Enabling profile metrics gathering
110+
`--collector.profile`
111+
To collect metrics, you need to enable the profiler in [MongoDB](https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/):
112+
Usage example: `db.setProfilingLevel(2)`
113+
114+
|Level|Description|
115+
|-----|-----------|
116+
|0| The profiler is off and does not collect any data. This is the default profiler level.|
117+
|1| The profiler collects data for operations that take longer than the value of `slowms` or that match a filter.<br> When a filter is set: <ul><li> The `slowms` and `sampleRate` options are not used for profiling.</li><li>The profiler only captures operations that match the filter.</li></ul>
118+
|2|The profiler collects data for all operations.|
109119

110120
#### Cluster role labels
111121
The exporter sets some topology labels in all metrics.

REFERENCE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@
2424
|--collector.collstats|Enable collecting metrics from $collStats|
2525
|--collect-all|Enable all collectors. Same as specifying all --collector.\<name\>|
2626
|--collector.collstats-limit=0|Disable collstats, dbstats, topmetrics and indexstats collector if there are more than \<n\> collections. 0=No limit|
27+
|--collector.profile-time-ts=30|Set time for scrape slow queries| This interval must be synchronized with the Prometheus scrape interval|
28+
|--collector.profile|Enable collecting metrics from profile|
2729
|--metrics.overridedescendingindex| Enable descending index name override to replace -1 with _DESC ||
2830
|--version|Show version and exit|

exporter/exporter.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Opts struct {
5959
DisableDefaultRegistry bool
6060
DiscoveringMode bool
6161
GlobalConnPool bool
62+
ProfileTimeTS int
6263

6364
CollectAll bool
6465
EnableDBStats bool
@@ -69,6 +70,7 @@ type Opts struct {
6970
EnableTopMetrics bool
7071
EnableIndexStats bool
7172
EnableCollStats bool
73+
EnableProfile bool
7274

7375
EnableOverrideDescendingIndex bool
7476

@@ -172,6 +174,7 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol
172174
e.opts.EnableReplicasetStatus = true
173175
e.opts.EnableIndexStats = true
174176
e.opts.EnableCurrentopMetrics = true
177+
e.opts.EnableProfile = true
175178
}
176179

177180
// arbiter only have isMaster privileges
@@ -183,6 +186,7 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol
183186
e.opts.EnableReplicasetStatus = false
184187
e.opts.EnableIndexStats = false
185188
e.opts.EnableCurrentopMetrics = false
189+
e.opts.EnableProfile = false
186190
}
187191

188192
// If we manually set the collection names we want or auto discovery is set.
@@ -219,6 +223,12 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol
219223
registry.MustRegister(coc)
220224
}
221225

226+
if e.opts.EnableProfile && nodeType != typeMongos && limitsOk && requestOpts.EnableProfile && e.opts.ProfileTimeTS != 0 {
227+
pc := newProfileCollector(ctx, client, e.opts.Logger,
228+
e.opts.CompatibleMode, topologyInfo, e.opts.ProfileTimeTS)
229+
registry.MustRegister(pc)
230+
}
231+
222232
if e.opts.EnableTopMetrics && nodeType != typeMongos && limitsOk && requestOpts.EnableTopMetrics {
223233
tc := newTopCollector(ctx, client, e.opts.Logger,
224234
e.opts.CompatibleMode, topologyInfo)
@@ -303,6 +313,8 @@ func (e *Exporter) Handler() http.Handler {
303313
requestOpts.EnableIndexStats = true
304314
case "collstats":
305315
requestOpts.EnableCollStats = true
316+
case "profile":
317+
requestOpts.EnableProfile = true
306318
}
307319
}
308320

exporter/profile_status_collector.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// mongodb_exporter
2+
// Copyright (C) 2017 Percona LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package exporter
17+
18+
import (
19+
"context"
20+
"time"
21+
22+
"github.com/pkg/errors"
23+
"github.com/prometheus/client_golang/prometheus"
24+
"github.com/sirupsen/logrus"
25+
"go.mongodb.org/mongo-driver/bson"
26+
"go.mongodb.org/mongo-driver/bson/primitive"
27+
"go.mongodb.org/mongo-driver/mongo"
28+
)
29+
30+
type profileCollector struct {
31+
ctx context.Context
32+
base *baseCollector
33+
compatibleMode bool
34+
topologyInfo labelsGetter
35+
profiletimets int
36+
}
37+
38+
// newProfileCollector creates a collector for being processed queries.
39+
func newProfileCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger,
40+
compatible bool, topology labelsGetter, profileTimeTS int,
41+
) *profileCollector {
42+
return &profileCollector{
43+
ctx: ctx,
44+
base: newBaseCollector(client, logger),
45+
compatibleMode: compatible,
46+
topologyInfo: topology,
47+
profiletimets: profileTimeTS,
48+
}
49+
}
50+
51+
func (d *profileCollector) Describe(ch chan<- *prometheus.Desc) {
52+
d.base.Describe(d.ctx, ch, d.collect)
53+
}
54+
55+
func (d *profileCollector) Collect(ch chan<- prometheus.Metric) {
56+
d.base.Collect(ch)
57+
}
58+
59+
func (d *profileCollector) collect(ch chan<- prometheus.Metric) {
60+
defer measureCollectTime(ch, "mongodb", "profile")()
61+
62+
logger := d.base.logger
63+
client := d.base.client
64+
timeScrape := d.profiletimets
65+
66+
databases, err := databases(d.ctx, client, nil, nil)
67+
if err != nil {
68+
errors.Wrap(err, "cannot get the database names list")
69+
return
70+
}
71+
72+
// Now time + '--collector.profile-time-ts'
73+
ts := primitive.NewDateTimeFromTime(time.Now().Add(-time.Duration(time.Second * time.Duration(timeScrape))))
74+
75+
labels := d.topologyInfo.baseLabels()
76+
77+
// Get all slow queries from all databases
78+
cmd := bson.M{"ts": bson.M{"$gte": ts}}
79+
for _, db := range databases {
80+
res, err := client.Database(db).Collection("system.profile").CountDocuments(d.ctx, cmd)
81+
if err != nil {
82+
errors.Wrapf(err, "cannot read system.profile")
83+
break
84+
}
85+
labels["database"] = db
86+
87+
m := primitive.M{"count": res}
88+
89+
logger.Debug("profile response from MongoDB:")
90+
debugResult(logger, primitive.M{db: m})
91+
92+
for _, metric := range makeMetrics("profile_slow_query", m, labels, d.compatibleMode) {
93+
ch <- metric
94+
}
95+
}
96+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// mongodb_exporter
2+
// Copyright (C) 2017 Percona LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package exporter
17+
18+
import (
19+
"context"
20+
"strings"
21+
"testing"
22+
"time"
23+
24+
"github.com/prometheus/client_golang/prometheus/testutil"
25+
"github.com/sirupsen/logrus"
26+
"github.com/stretchr/testify/assert"
27+
"go.mongodb.org/mongo-driver/bson"
28+
29+
"github.com/percona/mongodb_exporter/internal/tu"
30+
)
31+
32+
func TestProfileCollector(t *testing.T) {
33+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
34+
defer cancel()
35+
36+
client := tu.DefaultTestClient(ctx, t)
37+
38+
database := client.Database("testdb")
39+
database.Drop(ctx) //nolint
40+
41+
defer func() {
42+
err := database.Drop(ctx)
43+
assert.NoError(t, err)
44+
}()
45+
46+
// Enable database profiler https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/
47+
cmd := bson.M{"profile": 2}
48+
_ = database.RunCommand(ctx, cmd)
49+
50+
ti := labelsGetterMock{}
51+
52+
c := newProfileCollector(ctx, client, logrus.New(), false, ti, 30)
53+
54+
expected := strings.NewReader(`
55+
# HELP mongodb_profile_slow_query_count profile_slow_query.
56+
# TYPE mongodb_profile_slow_query_count counter
57+
mongodb_profile_slow_query_count{database="admin"} 0
58+
mongodb_profile_slow_query_count{database="config"} 0
59+
mongodb_profile_slow_query_count{database="local"} 0
60+
mongodb_profile_slow_query_count{database="testdb"} 0` +
61+
"\n")
62+
63+
filter := []string{
64+
"mongodb_profile_slow_query_count",
65+
}
66+
67+
err := testutil.CollectAndCompare(c, expected, filter...)
68+
assert.NoError(t, err)
69+
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ type GlobalFlags struct {
5454
EnableCurrentopMetrics bool `name:"collector.currentopmetrics" help:"Enable collecting metrics currentop admin command"`
5555
EnableIndexStats bool `name:"collector.indexstats" help:"Enable collecting metrics from $indexStats"`
5656
EnableCollStats bool `name:"collector.collstats" help:"Enable collecting metrics from $collStats"`
57+
EnableProfile bool `name:"collector.profile" help:"Enable collecting metrics from profile"`
5758

5859
EnableOverrideDescendingIndex bool `name:"metrics.overridedescendingindex" help:"Enable descending index name override to replace -1 with _DESC"`
5960

6061
CollectAll bool `name:"collect-all" help:"Enable all collectors. Same as specifying all --collector.<name>"`
6162

6263
CollStatsLimit int `name:"collector.collstats-limit" help:"Disable collstats, dbstats, topmetrics and indexstats collector if there are more than <n> collections. 0=No limit" default:"0"`
6364

65+
ProfileTimeTS int `name:"collector.profile-time-ts" help:"Set time for scrape slow queries." default:"30"`
66+
6467
DiscoveringMode bool `name:"discovering-mode" help:"Enable autodiscover collections" negatable:""`
6568
CompatibleMode bool `name:"compatible-mode" help:"Enable old mongodb-exporter compatible metrics" negatable:""`
6669
Version bool `name:"version" help:"Show version and exit"`
@@ -146,11 +149,13 @@ func buildExporter(opts GlobalFlags) *exporter.Exporter {
146149
EnableDBStatsFreeStorage: opts.EnableDBStatsFreeStorage,
147150
EnableIndexStats: opts.EnableIndexStats,
148151
EnableCollStats: opts.EnableCollStats,
152+
EnableProfile: opts.EnableProfile,
149153

150154
EnableOverrideDescendingIndex: opts.EnableOverrideDescendingIndex,
151155

152156
CollStatsLimit: opts.CollStatsLimit,
153157
CollectAll: opts.CollectAll,
158+
ProfileTimeTS: opts.ProfileTimeTS,
154159
}
155160

156161
e := exporter.New(exporterOpts)

0 commit comments

Comments
 (0)